From dbdc6e8273038b0cfc99a758bf2fcc4767e7aa5d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 31 Jul 2024 00:42:36 +0000 Subject: [PATCH] Added chart versions: confluent/confluent-for-kubernetes: - 0.1033.3 jenkins/jenkins: - 5.5.0 kubecost/cost-analyzer: - 2.3.4 new-relic/nri-bundle: - 5.0.88 speedscale/speedscale-operator: - 2.2.231 traefik/traefik: - 30.0.2 --- .../confluent-for-kubernetes-0.1033.3.tgz | Bin 0 -> 369839 bytes assets/jenkins/jenkins-5.5.0.tgz | Bin 0 -> 76988 bytes assets/kubecost/cost-analyzer-2.3.3.tgz | Bin 146314 -> 146309 bytes assets/kubecost/cost-analyzer-2.3.4.tgz | Bin 0 -> 146322 bytes assets/new-relic/nri-bundle-5.0.88.tgz | Bin 0 -> 341397 bytes .../speedscale-operator-2.2.231.tgz | Bin 0 -> 16665 bytes assets/traefik/traefik-30.0.2.tgz | Bin 0 -> 222585 bytes .../0.1033.3/Chart.yaml | 23 + .../0.1033.3/README.md | 72 + .../0.1033.3/app-readme.md | 3 + .../platform.confluent.io_clusterlinks.yaml | 883 ++ ...rm.confluent.io_confluentrolebindings.yaml | 296 + .../platform.confluent.io_connectors.yaml | 496 + .../crds/platform.confluent.io_connects.yaml | 6941 ++++++++++ .../platform.confluent.io_controlcenters.yaml | 6393 +++++++++ ...latform.confluent.io_kafkarestclasses.yaml | 557 + ...latform.confluent.io_kafkarestproxies.yaml | 5834 ++++++++ .../crds/platform.confluent.io_kafkas.yaml | 10942 ++++++++++++++++ .../platform.confluent.io_kafkatopics.yaml | 410 + ...latform.confluent.io_kraftcontrollers.yaml | 5748 ++++++++ ...tform.confluent.io_kraftmigrationjobs.yaml | 180 + .../crds/platform.confluent.io_ksqldbs.yaml | 6646 ++++++++++ ...platform.confluent.io_schemaexporters.yaml | 688 + ...latform.confluent.io_schemaregistries.yaml | 5801 ++++++++ .../crds/platform.confluent.io_schemas.yaml | 590 + .../platform.confluent.io_zookeepers.yaml | 4713 +++++++ .../0.1033.3/templates/NOTES.txt | 4 + .../0.1033.3/templates/_helpers.tpl | 42 + .../0.1033.3/templates/clusterrole.yaml | 172 + .../templates/clusterrolebinding.yaml | 56 + .../0.1033.3/templates/deployment.yaml | 238 + .../0.1033.3/templates/licensing.yaml | 19 + .../0.1033.3/templates/service.yaml | 28 + .../0.1033.3/templates/serviceaccount.yaml | 18 + .../validatingwebhookconfiguration.yaml | 184 + .../0.1033.3/values.yaml | 269 + charts/jenkins/jenkins/5.5.0/CHANGELOG.md | 3041 +++++ charts/jenkins/jenkins/5.5.0/Chart.yaml | 54 + charts/jenkins/jenkins/5.5.0/README.md | 706 + charts/jenkins/jenkins/5.5.0/UPGRADING.md | 148 + charts/jenkins/jenkins/5.5.0/VALUES.md | 311 + charts/jenkins/jenkins/5.5.0/VALUES.md.gotmpl | 28 + .../jenkins/jenkins/5.5.0/templates/NOTES.txt | 68 + .../jenkins/5.5.0/templates/_helpers.tpl | 673 + .../5.5.0/templates/auto-reload-config.yaml | 60 + .../5.5.0/templates/config-init-scripts.yaml | 18 + .../jenkins/5.5.0/templates/config.yaml | 92 + .../jenkins/5.5.0/templates/deprecation.yaml | 151 + .../jenkins/5.5.0/templates/home-pvc.yaml | 41 + .../jenkins/5.5.0/templates/jcasc-config.yaml | 53 + .../5.5.0/templates/jenkins-agent-svc.yaml | 43 + .../jenkins-aws-security-group-policies.yaml | 16 + .../jenkins-controller-alerting-rules.yaml | 26 + .../jenkins-controller-backendconfig.yaml | 24 + .../templates/jenkins-controller-ingress.yaml | 77 + .../jenkins-controller-networkpolicy.yaml | 76 + .../templates/jenkins-controller-pdb.yaml | 34 + .../jenkins-controller-podmonitor.yaml | 30 + .../templates/jenkins-controller-route.yaml | 34 + .../jenkins-controller-secondary-ingress.yaml | 56 + .../jenkins-controller-servicemonitor.yaml | 45 + .../jenkins-controller-statefulset.yaml | 424 + .../templates/jenkins-controller-svc.yaml | 56 + .../jenkins/jenkins/5.5.0/templates/rbac.yaml | 149 + .../5.5.0/templates/secret-additional.yaml | 21 + .../5.5.0/templates/secret-claims.yaml | 29 + .../5.5.0/templates/secret-https-jks.yaml | 20 + .../jenkins/5.5.0/templates/secret.yaml | 20 + .../templates/service-account-agent.yaml | 26 + .../5.5.0/templates/service-account.yaml | 26 + .../5.5.0/templates/tests/jenkins-test.yaml | 49 + .../5.5.0/templates/tests/test-config.yaml | 14 + charts/jenkins/jenkins/5.5.0/values.yaml | 1337 ++ .../kubecost/cost-analyzer/2.3.3/Chart.yaml | 1 - .../kubecost/cost-analyzer/2.3.4/Chart.yaml | 14 + charts/kubecost/cost-analyzer/2.3.4/README.md | 116 + .../cost-analyzer/2.3.4/app-readme.md | 25 + .../2.3.4/ci/aggregator-values.yaml | 17 + .../federatedetl-primary-netcosts-values.yaml | 35 + .../2.3.4/ci/statefulsets-cc.yaml | 46 + .../2.3.4/crds/cluster-turndown-crd.yaml | 78 + .../cost-analyzer/2.3.4/custom-pricing.csv | 7 + .../2.3.4/grafana-dashboards/README.md | 45 + .../grafana-dashboards/attached-disks.json | 549 + .../grafana-dashboards/cluster-metrics.json | 1683 +++ .../cluster-utilization.json | 3196 +++++ .../deployment-utilization.json | 1386 ++ .../aggregator-dashboard.json | 668 + .../multi-cluster-container-stats.json | 787 ++ .../multi-cluster-disk-usage.json | 571 + .../multi-cluster-network-transfer-data.json | 685 + .../kubernetes-resource-efficiency.json | 408 + .../label-cost-utilization.json | 1146 ++ .../namespace-utilization.json | 1175 ++ .../network-cloud-services.json | 408 + .../networkCosts-metrics.json | 672 + .../grafana-dashboards/node-utilization.json | 1389 ++ .../pod-utilization-multi-cluster.json | 788 ++ .../grafana-dashboards/pod-utilization.json | 757 ++ .../grafana-dashboards/prom-benchmark.json | 5691 ++++++++ .../workload-metrics-aggregator.json | 988 ++ .../grafana-dashboards/workload-metrics.json | 893 ++ .../cost-analyzer/2.3.4/questions.yaml | 187 + .../create-admission-controller-tls.sh | 29 + .../cost-analyzer/2.3.4/templates/NOTES.txt | 27 + .../2.3.4/templates/_helpers.tpl | 1463 +++ .../aggregator-cloud-cost-deployment.yaml | 167 + ...aggregator-cloud-cost-service-account.yaml | 23 + .../aggregator-cloud-cost-service.yaml | 17 + .../2.3.4/templates/aggregator-service.yaml | 25 + .../templates/aggregator-servicemonitor.yaml | 31 + .../templates/aggregator-statefulset.yaml | 199 + .../templates/alibaba-service-key-secret.yaml | 20 + .../templates/aws-service-key-secret.yaml | 20 + .../awsstore-deployment-template.yaml | 49 + .../awsstore-service-account-template.yaml | 15 + .../templates/azure-service-key-secret.yaml | 24 + .../azure-storage-config-secret.yaml | 26 + .../templates/cloud-integration-secret.yaml | 16 + ...st-analyzer-account-mapping-configmap.yaml | 12 + ...t-analyzer-advanced-reports-configmap.yaml | 13 + .../cost-analyzer-alerts-configmap.yaml | 10 + ...cost-analyzer-asset-reports-configmap.yaml | 14 + ...analyzer-cloud-cost-reports-configmap.yaml | 13 + ...nalyzer-cluster-role-binding-template.yaml | 36 + ...alyzer-cluster-role-template-readonly.yaml | 26 + .../cost-analyzer-cluster-role-template.yaml | 108 + .../cost-analyzer-config-map-template.yaml | 35 + .../cost-analyzer-db-pvc-template.yaml | 35 + .../cost-analyzer-deployment-template.yaml | 1191 ++ ...analyzer-frontend-config-map-template.yaml | 1328 ++ .../cost-analyzer-ingress-template.yaml | 56 + ...-analyzer-metrics-config-map-template.yaml | 13 + ...zer-network-costs-config-map-template.yaml | 16 + ...zer-network-costs-podmonitor-template.yaml | 32 + ...alyzer-network-costs-service-template.yaml | 34 + .../cost-analyzer-network-costs-template.yaml | 149 + ...cost-analyzer-network-policy-template.yaml | 47 + .../cost-analyzer-network-policy.yaml | 48 + .../cost-analyzer-networks-costs-ocp-scc.yaml | 30 + .../templates/cost-analyzer-ocp-route.yaml | 25 + ...ost-analyzer-oidc-config-map-template.yaml | 48 + .../cost-analyzer-pkey-configmap.yaml | 23 + .../cost-analyzer-pricing-configmap.yaml | 141 + ...cost-analyzer-prometheusrule-template.yaml | 22 + .../templates/cost-analyzer-pvc-template.yaml | 33 + ...ost-analyzer-saml-config-map-template.yaml | 14 + ...cost-analyzer-saved-reports-configmap.yaml | 13 + .../cost-analyzer-server-configmap.yaml | 72 + ...ost-analyzer-service-account-template.yaml | 13 + .../cost-analyzer-service-template.yaml | 66 + ...cost-analyzer-servicemonitor-template.yaml | 34 + .../cost-analyzer-smtp-configmap.yaml | 12 + .../templates/diagnostics-deployment.yaml | 177 + .../2.3.4/templates/diagnostics-service.yaml | 20 + .../2.3.4/templates/etl-utils-deployment.yaml | 123 + .../2.3.4/templates/etl-utils-service.yaml | 18 + .../external-grafana-config-map-template.yaml | 11 + .../2.3.4/templates/extra-manifests.yaml | 8 + .../templates/forecasting-deployment.yaml | 145 + .../2.3.4/templates/forecasting-service.yaml | 17 + .../frontend-deployment-template.yaml | 218 + .../templates/frontend-service-template.yaml | 53 + .../gcpstore-config-map-template.yaml | 61 + .../2.3.4/templates/grafana-clusterrole.yaml | 24 + .../templates/grafana-clusterrolebinding.yaml | 24 + .../grafana-configmap-dashboard-provider.yaml | 28 + .../2.3.4/templates/grafana-configmap.yaml | 90 + .../grafana-dashboard-attached-disks.yaml | 21 + ...na-dashboard-cluster-metrics-template.yaml | 21 + ...ashboard-cluster-utilization-template.yaml | 21 + ...board-deployment-utilization-template.yaml | 21 + ...bernetes-resource-efficiency-template.yaml | 21 + ...board-label-cost-utilization-template.yaml | 21 + ...hboard-namespace-utilization-template.yaml | 21 + ...afana-dashboard-network-cloud-sevices.yaml | 21 + .../grafana-dashboard-network-costs.yaml | 21 + ...a-dashboard-node-utilization-template.yaml | 21 + ...shboard-pod-utilization-multi-cluster.yaml | 21 + ...na-dashboard-pod-utilization-template.yaml | 21 + ...dashboard-prometheus-metrics-template.yaml | 21 + ...grafana-dashboard-workload-aggregator.yaml | 21 + .../grafana-dashboard-workload-metrics.yaml | 21 + .../grafana-dashboards-json-configmap.yaml | 24 + .../grafana-datasource-template.yaml | 38 + .../2.3.4/templates/grafana-deployment.yaml | 313 + .../2.3.4/templates/grafana-ingress.yaml | 47 + .../2.3.4/templates/grafana-pvc.yaml | 26 + .../2.3.4/templates/grafana-secret.yaml | 22 + .../2.3.4/templates/grafana-service.yaml | 51 + .../templates/grafana-serviceaccount.yaml | 13 + .../2.3.4/templates/install-plugins.yaml | 43 + ...tegrations-postgres-queries-configmap.yaml | 14 + .../integrations-postgres-secret.yaml | 19 + ...admission-controller-service-template.yaml | 15 + ...ubecost-admission-controller-template.yaml | 30 + .../kubecost-agent-secret-template.yaml | 12 + ...ubecost-agent-secretprovider-template.yaml | 25 + ...ost-cluster-controller-actions-config.yaml | 56 + .../kubecost-cluster-controller-template.yaml | 293 + ...st-cluster-manager-configmap-template.yaml | 14 + .../kubecost-metrics-deployment-template.yaml | 341 + ...cost-metrics-service-monitor-template.yaml | 41 + .../kubecost-metrics-service-template.yaml | 34 + .../kubecost-oidc-secret-template.yaml | 16 + .../kubecost-priority-class-template.yaml | 15 + .../kubecost-saml-secret-template.yaml | 12 + .../mimir-proxy-configmap-template.yaml | 21 + .../mimir-proxy-deployment-template.yaml | 46 + .../mimir-proxy-service-template.yaml | 18 + .../templates/model-ingress-template.yaml | 51 + ...network-costs-servicemonitor-template.yaml | 32 + .../2.3.4/templates/plugins-config.yaml | 13 + .../prometheus-alertmanager-configmap.yaml | 21 + .../prometheus-alertmanager-deployment.yaml | 148 + .../prometheus-alertmanager-ingress.yaml | 41 + ...prometheus-alertmanager-networkpolicy.yaml | 22 + .../prometheus-alertmanager-pdb.yaml | 16 + .../prometheus-alertmanager-pvc.yaml | 35 + ...metheus-alertmanager-service-headless.yaml | 33 + .../prometheus-alertmanager-service.yaml | 55 + ...rometheus-alertmanager-serviceaccount.yaml | 11 + .../prometheus-alertmanager-statefulset.yaml | 155 + .../prometheus-node-exporter-daemonset.yaml | 139 + .../prometheus-node-exporter-ocp-scc.yaml | 29 + .../prometheus-node-exporter-service.yaml | 47 + ...ometheus-node-exporter-serviceaccount.yaml | 11 + .../prometheus-pushgateway-deployment.yaml | 106 + .../prometheus-pushgateway-ingress.yaml | 38 + .../prometheus-pushgateway-networkpolicy.yaml | 22 + .../templates/prometheus-pushgateway-pdb.yaml | 15 + .../templates/prometheus-pushgateway-pvc.yaml | 35 + .../prometheus-pushgateway-service.yaml | 43 + ...prometheus-pushgateway-serviceaccount.yaml | 11 + .../prometheus-server-clusterrole.yaml | 39 + .../prometheus-server-clusterrolebinding.yaml | 18 + .../prometheus-server-configmap.yaml | 90 + .../prometheus-server-deployment.yaml | 265 + .../templates/prometheus-server-ingress.yaml | 45 + .../prometheus-server-networkpolicy.yaml | 16 + .../templates/prometheus-server-pdb.yaml | 15 + .../templates/prometheus-server-pvc.yaml | 37 + .../prometheus-server-service-headless.yaml | 29 + .../templates/prometheus-server-service.yaml | 62 + .../prometheus-server-serviceaccount.yaml | 17 + .../prometheus-server-statefulset.yaml | 227 + .../templates/prometheus-server-vpa.yaml | 22 + .../2.3.4/templates/tests/_helpers.tpl | 5 + .../2.3.4/templates/tests/basic-health.yaml | 48 + .../cost-analyzer/2.3.4/values-agent.yaml | 113 + .../cost-analyzer/2.3.4/values-amp.yaml | 20 + .../2.3.4/values-cloud-agent.yaml | 45 + .../2.3.4/values-custom-pricing.yaml | 17 + .../2.3.4/values-eks-cost-monitoring.yaml | 43 + .../cost-analyzer/2.3.4/values-openshift.yaml | 25 + .../2.3.4/values-windows-node-affinity.yaml | 30 + .../kubecost/cost-analyzer/2.3.4/values.yaml | 3453 +++++ .../new-relic/nri-bundle/5.0.88/.helmignore | 22 + charts/new-relic/nri-bundle/5.0.88/Chart.lock | 39 + charts/new-relic/nri-bundle/5.0.88/Chart.yaml | 85 + charts/new-relic/nri-bundle/5.0.88/README.md | 200 + .../nri-bundle/5.0.88/README.md.gotmpl | 166 + .../new-relic/nri-bundle/5.0.88/app-readme.md | 5 + .../charts/k8s-agents-operator/.helmignore | 23 + .../charts/k8s-agents-operator/Chart.yaml | 16 + .../charts/k8s-agents-operator/README.md | 191 + .../k8s-agents-operator/README.md.gotmpl | 157 + .../k8s-agents-operator/templates/NOTES.txt | 36 + .../templates/_helpers.tpl | 80 + .../templates/certmanager.yaml | 17 + .../templates/deployment.yaml | 91 + .../templates/instrumentation-crd.yaml | 1150 ++ .../templates/leader-election-rbac.yaml | 49 + .../templates/manager-rbac.yaml | 76 + .../mutating-webhook-configuration.yaml | 49 + .../templates/newrelic_license_secret.yaml | 14 + .../templates/proxy-rbac.yaml | 34 + .../templates/reader-rbac.yaml | 11 + .../templates/selfsigned-issuer.yaml | 8 + .../templates/service.yaml | 15 + .../validating-webhook-configuration.yaml | 48 + .../templates/webhook-service.yaml | 15 + .../charts/k8s-agents-operator/values.yaml | 62 + .../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 | 279 + .../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 | 49 + .../templates/serviceaccount.yaml | 15 + .../templates/servicemonitor.yaml | 114 + .../templates/stsdiscovery-role.yaml | 26 + .../templates/stsdiscovery-rolebinding.yaml | 17 + .../templates/verticalpodautoscaler.yaml | 44 + .../charts/kube-state-metrics/values.yaml | 441 + .../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 | 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-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 | 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 + .../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 | 602 + .../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 | 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-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.88/charts/newrelic-logging/Chart.lock | 6 + .../5.0.88/charts/newrelic-logging/Chart.yaml | 20 + .../5.0.88/charts/newrelic-logging/README.md | 267 + .../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 | 171 + .../newrelic-logging/templates/daemonset.yaml | 208 + .../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 + .../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 | 357 + .../5.0.88/charts/newrelic-pixie/Chart.yaml | 18 + .../5.0.88/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 | 164 + .../newrelic-pixie/templates/secret.yaml | 20 + .../newrelic-pixie/tests/configmap.yaml | 44 + .../charts/newrelic-pixie/tests/jobs.yaml | 138 + .../5.0.88/charts/newrelic-pixie/values.yaml | 70 + .../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 | 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-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.88/charts/nri-kube-events/Chart.lock | 6 + .../5.0.88/charts/nri-kube-events/Chart.yaml | 26 + .../5.0.88/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 | 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-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.88/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 | 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-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.88/charts/nri-prometheus/.helmignore | 22 + .../5.0.88/charts/nri-prometheus/Chart.lock | 6 + .../5.0.88/charts/nri-prometheus/Chart.yaml | 29 + .../5.0.88/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 | 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-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.88/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.88/ci/test-values.yaml | 21 + .../nri-bundle/5.0.88/questions.yaml | 113 + .../new-relic/nri-bundle/5.0.88/values.yaml | 169 + .../speedscale-operator/2.2.231/.helmignore | 23 + .../speedscale-operator/2.2.231/Chart.yaml | 27 + .../speedscale-operator/2.2.231/LICENSE | 201 + .../speedscale-operator/2.2.231/README.md | 108 + .../speedscale-operator/2.2.231/app-readme.md | 108 + .../2.2.231/questions.yaml | 9 + .../2.2.231/templates/NOTES.txt | 12 + .../2.2.231/templates/admission.yaml | 199 + .../2.2.231/templates/configmap.yaml | 41 + .../templates/crds/trafficreplays.yaml | 514 + .../2.2.231/templates/deployments.yaml | 132 + .../2.2.231/templates/hooks.yaml | 73 + .../2.2.231/templates/rbac.yaml | 244 + .../2.2.231/templates/secrets.yaml | 18 + .../2.2.231/templates/services.yaml | 22 + .../2.2.231/templates/tls.yaml | 183 + .../speedscale-operator/2.2.231/values.yaml | 133 + charts/traefik/traefik/30.0.2/.helmignore | 2 + charts/traefik/traefik/30.0.2/Changelog.md | 9135 +++++++++++++ charts/traefik/traefik/30.0.2/Chart.yaml | 31 + charts/traefik/traefik/30.0.2/EXAMPLES.md | 994 ++ charts/traefik/traefik/30.0.2/Guidelines.md | 92 + charts/traefik/traefik/30.0.2/LICENSE | 202 + charts/traefik/traefik/30.0.2/README.md | 158 + charts/traefik/traefik/30.0.2/VALUES.md | 284 + charts/traefik/traefik/30.0.2/app-readme.md | 5 + ....networking.k8s.io_backendtlspolicies.yaml | 281 + ...eway.networking.k8s.io_gatewayclasses.yaml | 381 + .../gateway.networking.k8s.io_gateways.yaml | 1037 ++ .../gateway.networking.k8s.io_grpcroutes.yaml | 819 ++ .../gateway.networking.k8s.io_httproutes.yaml | 2263 ++++ ...way.networking.k8s.io_referencegrants.yaml | 205 + .../gateway.networking.k8s.io_tcproutes.yaml | 284 + .../gateway.networking.k8s.io_tlsroutes.yaml | 294 + .../gateway.networking.k8s.io_udproutes.yaml | 284 + .../hub.traefik.io_accesscontrolpolicies.yaml | 368 + .../crds/hub.traefik.io_apiaccesses.yaml | 153 + .../crds/hub.traefik.io_apiportals.yaml | 139 + .../crds/hub.traefik.io_apiratelimits.yaml | 166 + .../30.0.2/crds/hub.traefik.io_apis.yaml | 190 + .../crds/hub.traefik.io_apiversions.yaml | 194 + .../30.0.2/crds/traefik.io_ingressroutes.yaml | 366 + .../crds/traefik.io_ingressroutetcps.yaml | 247 + .../crds/traefik.io_ingressrouteudps.yaml | 111 + .../30.0.2/crds/traefik.io_middlewares.yaml | 1098 ++ .../crds/traefik.io_middlewaretcps.yaml | 87 + .../crds/traefik.io_serverstransports.yaml | 139 + .../crds/traefik.io_serverstransporttcps.yaml | 120 + .../30.0.2/crds/traefik.io_tlsoptions.yaml | 114 + .../30.0.2/crds/traefik.io_tlsstores.yaml | 97 + .../crds/traefik.io_traefikservices.yaml | 639 + .../traefik/30.0.2/templates/NOTES.txt | 36 + .../traefik/30.0.2/templates/_helpers.tpl | 161 + .../traefik/30.0.2/templates/_podtemplate.tpl | 829 ++ .../30.0.2/templates/_service-metrics.tpl | 25 + .../traefik/30.0.2/templates/_service.tpl | 84 + .../traefik/30.0.2/templates/daemonset.yaml | 50 + .../traefik/30.0.2/templates/deployment.yaml | 54 + .../30.0.2/templates/extra-objects.yaml | 4 + .../traefik/30.0.2/templates/gateway.yaml | 52 + .../30.0.2/templates/gatewayclass.yaml | 14 + .../traefik/traefik/30.0.2/templates/hpa.yaml | 35 + .../templates/hub-admission-controller.yaml | 250 + .../30.0.2/templates/hub-apiportal.yaml | 19 + .../30.0.2/templates/ingressclass.yaml | 12 + .../30.0.2/templates/ingressroute.yaml | 43 + .../30.0.2/templates/poddisruptionbudget.yaml | 23 + .../30.0.2/templates/prometheusrules.yaml | 28 + .../30.0.2/templates/provider-file-cm.yaml | 12 + .../traefik/traefik/30.0.2/templates/pvc.yaml | 26 + .../30.0.2/templates/rbac/clusterrole.yaml | 286 + .../templates/rbac/clusterrolebinding.yaml | 24 + .../templates/rbac/podsecuritypolicy.yaml | 68 + .../traefik/30.0.2/templates/rbac/role.yaml | 168 + .../30.0.2/templates/rbac/rolebinding.yaml | 25 + .../30.0.2/templates/rbac/serviceaccount.yaml | 14 + .../30.0.2/templates/requirements.yaml | 20 + .../30.0.2/templates/service-metrics.yaml | 33 + .../traefik/30.0.2/templates/service.yaml | 75 + .../30.0.2/templates/servicemonitor.yaml | 69 + .../traefik/30.0.2/templates/tlsoption.yaml | 39 + .../traefik/30.0.2/templates/tlsstore.yaml | 12 + charts/traefik/traefik/30.0.2/values.yaml | 970 ++ index.yaml | 261 +- 886 files changed, 177156 insertions(+), 3 deletions(-) create mode 100644 assets/confluent/confluent-for-kubernetes-0.1033.3.tgz create mode 100644 assets/jenkins/jenkins-5.5.0.tgz create mode 100644 assets/kubecost/cost-analyzer-2.3.4.tgz create mode 100644 assets/new-relic/nri-bundle-5.0.88.tgz create mode 100644 assets/speedscale/speedscale-operator-2.2.231.tgz create mode 100644 assets/traefik/traefik-30.0.2.tgz create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/Chart.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/README.md create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/app-readme.md create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_clusterlinks.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_confluentrolebindings.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_connectors.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_connects.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_controlcenters.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kafkarestclasses.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kafkarestproxies.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kafkas.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kafkatopics.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kraftcontrollers.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kraftmigrationjobs.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_ksqldbs.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_schemaexporters.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_schemaregistries.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_schemas.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_zookeepers.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/templates/NOTES.txt create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/templates/_helpers.tpl create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/templates/clusterrole.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/templates/clusterrolebinding.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/templates/deployment.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/templates/licensing.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/templates/service.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/templates/serviceaccount.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/templates/validatingwebhookconfiguration.yaml create mode 100644 charts/confluent/confluent-for-kubernetes/0.1033.3/values.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/CHANGELOG.md create mode 100644 charts/jenkins/jenkins/5.5.0/Chart.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/README.md create mode 100644 charts/jenkins/jenkins/5.5.0/UPGRADING.md create mode 100644 charts/jenkins/jenkins/5.5.0/VALUES.md create mode 100644 charts/jenkins/jenkins/5.5.0/VALUES.md.gotmpl create mode 100644 charts/jenkins/jenkins/5.5.0/templates/NOTES.txt create mode 100644 charts/jenkins/jenkins/5.5.0/templates/_helpers.tpl create mode 100644 charts/jenkins/jenkins/5.5.0/templates/auto-reload-config.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/config-init-scripts.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/config.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/deprecation.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/home-pvc.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/jcasc-config.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/jenkins-agent-svc.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/jenkins-aws-security-group-policies.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-alerting-rules.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-backendconfig.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-ingress.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-networkpolicy.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-pdb.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-podmonitor.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-route.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-secondary-ingress.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-servicemonitor.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-statefulset.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-svc.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/rbac.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/secret-additional.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/secret-claims.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/secret-https-jks.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/secret.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/service-account-agent.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/service-account.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/tests/jenkins-test.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/templates/tests/test-config.yaml create mode 100644 charts/jenkins/jenkins/5.5.0/values.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/Chart.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/README.md create mode 100644 charts/kubecost/cost-analyzer/2.3.4/app-readme.md create mode 100644 charts/kubecost/cost-analyzer/2.3.4/ci/aggregator-values.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/ci/federatedetl-primary-netcosts-values.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/ci/statefulsets-cc.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/crds/cluster-turndown-crd.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/custom-pricing.csv create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/README.md create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/attached-disks.json create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/cluster-metrics.json create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/cluster-utilization.json create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/deployment-utilization.json create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/grafana-templates/aggregator-dashboard.json create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/grafana-templates/multi-cluster-container-stats.json create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/grafana-templates/multi-cluster-disk-usage.json create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/grafana-templates/multi-cluster-network-transfer-data.json create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/kubernetes-resource-efficiency.json create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/label-cost-utilization.json create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/namespace-utilization.json create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/network-cloud-services.json create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/networkCosts-metrics.json create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/node-utilization.json create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/pod-utilization-multi-cluster.json create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/pod-utilization.json create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/prom-benchmark.json create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/workload-metrics-aggregator.json create mode 100644 charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/workload-metrics.json create mode 100644 charts/kubecost/cost-analyzer/2.3.4/questions.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/scripts/create-admission-controller-tls.sh create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/NOTES.txt create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/_helpers.tpl create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-cloud-cost-deployment.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-cloud-cost-service-account.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-cloud-cost-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-servicemonitor.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-statefulset.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/alibaba-service-key-secret.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/aws-service-key-secret.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/awsstore-deployment-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/awsstore-service-account-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/azure-service-key-secret.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/azure-storage-config-secret.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cloud-integration-secret.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-account-mapping-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-advanced-reports-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-alerts-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-asset-reports-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-cloud-cost-reports-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-cluster-role-binding-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-cluster-role-template-readonly.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-cluster-role-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-config-map-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-db-pvc-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-deployment-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-frontend-config-map-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-ingress-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-metrics-config-map-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-costs-config-map-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-costs-podmonitor-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-costs-service-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-costs-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-policy-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-policy.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-networks-costs-ocp-scc.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-ocp-route.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-oidc-config-map-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-pkey-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-pricing-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-prometheusrule-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-pvc-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-saml-config-map-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-saved-reports-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-server-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-service-account-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-service-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-servicemonitor-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-smtp-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/diagnostics-deployment.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/diagnostics-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/etl-utils-deployment.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/etl-utils-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/external-grafana-config-map-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/extra-manifests.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/forecasting-deployment.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/forecasting-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/frontend-deployment-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/frontend-service-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/gcpstore-config-map-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-clusterrole.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-clusterrolebinding.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-configmap-dashboard-provider.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-attached-disks.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-cluster-metrics-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-cluster-utilization-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-deployment-utilization-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-kubernetes-resource-efficiency-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-label-cost-utilization-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-namespace-utilization-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-network-cloud-sevices.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-network-costs.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-node-utilization-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-pod-utilization-multi-cluster.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-pod-utilization-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-prometheus-metrics-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-workload-aggregator.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-workload-metrics.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboards-json-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-datasource-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-deployment.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-ingress.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-pvc.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-secret.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/grafana-serviceaccount.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/install-plugins.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/integrations-postgres-queries-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/integrations-postgres-secret.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-admission-controller-service-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-admission-controller-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-agent-secret-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-agent-secretprovider-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-cluster-controller-actions-config.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-cluster-controller-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-cluster-manager-configmap-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-metrics-deployment-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-metrics-service-monitor-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-metrics-service-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-oidc-secret-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-priority-class-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-saml-secret-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/mimir-proxy-configmap-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/mimir-proxy-deployment-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/mimir-proxy-service-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/model-ingress-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/network-costs-servicemonitor-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/plugins-config.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-deployment.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-ingress.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-networkpolicy.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-pdb.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-pvc.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-service-headless.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-serviceaccount.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-statefulset.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-node-exporter-daemonset.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-node-exporter-ocp-scc.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-node-exporter-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-node-exporter-serviceaccount.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-deployment.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-ingress.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-networkpolicy.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-pdb.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-pvc.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-serviceaccount.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-clusterrole.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-clusterrolebinding.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-deployment.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-ingress.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-networkpolicy.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-pdb.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-pvc.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-service-headless.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-serviceaccount.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-statefulset.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-vpa.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/tests/_helpers.tpl create mode 100644 charts/kubecost/cost-analyzer/2.3.4/templates/tests/basic-health.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/values-agent.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/values-amp.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/values-cloud-agent.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/values-custom-pricing.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/values-eks-cost-monitoring.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/values-openshift.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/values-windows-node-affinity.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.4/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.88/Chart.lock create mode 100644 charts/new-relic/nri-bundle/5.0.88/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/README.md.gotmpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/app-readme.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/README.md.gotmpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/NOTES.txt create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/certmanager.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/deployment.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/instrumentation-crd.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/leader-election-rbac.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/manager-rbac.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/mutating-webhook-configuration.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/newrelic_license_secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/proxy-rbac.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/reader-rbac.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/selfsigned-issuer.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/service.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/validating-webhook-configuration.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/webhook-service.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/NOTES.txt create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/ciliumnetworkpolicy.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/crs-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/deployment.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/extra-manifests.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/kubeconfig-secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/networkpolicy.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/pdb.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/podsecuritypolicy.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/psp-clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/psp-clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/rbac-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/role.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/rolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/service.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/servicemonitor.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/stsdiscovery-role.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/stsdiscovery-rolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/verticalpodautoscaler.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/Chart.lock create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/README.md.gotmpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/DEVELOPERS.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_affinity.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_agent-config.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_cluster.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_custom-attributes.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_dnsconfig.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_fedramp.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_hostnetwork.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_images.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_insights.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_insights_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_labels.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_license.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_license_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_low-data-mode.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_nodeselector.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_priority-class-name.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_privileged.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_proxy.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_security-context.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_serviceaccount.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_staging.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_tolerations.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_verbose-log.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/ci/test-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/NOTES.txt create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/job-createSecret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/job-patchWebhook.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/psp.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/role.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/rolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/admission-webhooks/mutatingWebhookConfiguration.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/cert-manager.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/deployment.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/service.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/tests/deployment_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/tests/job_patch_psp_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/tests/job_serviceaccount_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/tests/rbac_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/Chart.lock create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/README.md.gotmpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/DEVELOPERS.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_affinity.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_agent-config.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_cluster.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_custom-attributes.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_dnsconfig.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_fedramp.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_hostnetwork.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_images.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_insights.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_insights_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_labels.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_license.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_license_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_low-data-mode.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_nodeselector.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_priority-class-name.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_privileged.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_proxy.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_security-context.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_serviceaccount.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_staging.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_tolerations.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_verbose-log.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/ci/test-cplane-kind-deployment-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/ci/test-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/NOTES.txt create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/_helpers_compatibility.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/_affinity_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/_agent-config_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/_host_network.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/_rbac.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/_tolerations_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/agent-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/daemonset.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/rolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/scraper-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/ksm/_affinity_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/ksm/_agent-config_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/ksm/_host_network.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/ksm/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/ksm/_tolerations_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/ksm/agent-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/ksm/deployment.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/ksm/scraper-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/kubelet/_affinity_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/kubelet/_agent-config_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/kubelet/_host_network.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/kubelet/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/kubelet/_security_context_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/kubelet/_tolerations_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/kubelet/agent-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/kubelet/daemonset.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/kubelet/integrations-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/kubelet/scraper-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/podsecuritypolicy.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/Chart.lock create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/README.md.gotmpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/DEVELOPERS.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_affinity.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_agent-config.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_cluster.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_custom-attributes.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_dnsconfig.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_fedramp.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_hostnetwork.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_images.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_insights.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_insights_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_labels.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_license.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_license_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_low-data-mode.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_nodeselector.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_priority-class-name.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_privileged.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_proxy.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_security-context.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_serviceaccount.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_staging.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_tolerations.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_verbose-log.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/ci/test-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/templates/adapter-clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/templates/adapter-rolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/templates/apiservice/apiservice.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/job-createSecret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/job-patchAPIService.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/psp.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/role.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/rolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/templates/configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/templates/deployment.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/templates/hpa-clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/templates/hpa-clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/templates/secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/templates/service.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/templates/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/tests/apiservice_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/tests/common_extra_naming_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/tests/configmap_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/tests/deployment_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/tests/hpa_clusterrolebinding_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/tests/job_patch_cluster_rolebinding_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/tests/job_patch_clusterrole_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/tests/job_patch_common_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/tests/job_patch_job_createsecret_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/tests/job_patch_job_patchapiservice_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/tests/job_serviceaccount_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/tests/rbac_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/Chart.lock create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/DEVELOPERS.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_affinity.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_agent-config.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_cluster.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_custom-attributes.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_dnsconfig.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_fedramp.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_hostnetwork.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_images.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_insights.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_insights_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_labels.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_license.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_license_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_low-data-mode.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_nodeselector.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_priority-class-name.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_privileged.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_proxy.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_security-context.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_serviceaccount.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_staging.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_tolerations.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/templates/_verbose-log.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/ci/test-enable-windows-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/ci/test-lowdatamode-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/ci/test-override-global-lowdatamode.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/ci/test-staging-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/ci/test-with-empty-global.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/ci/test-with-empty-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/fluent-bit-and-plugin-metrics-dashboard-template.json create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/NOTES.txt create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/daemonset-windows.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/daemonset.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/persistentvolume.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/podsecuritypolicy.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/tests/cri_parser_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/tests/dns_config_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/tests/endpoint_region_selection_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/tests/fluentbit_k8logging_exclude_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/tests/fluentbit_persistence_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/tests/fluentbit_pod_label_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/tests/fluentbit_sendmetrics_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/tests/images_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/tests/linux_volume_mount_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/tests/rbac_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/ci/test-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/templates/NOTES.txt create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/templates/configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/templates/job.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/templates/secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/tests/configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/tests/jobs.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/Chart.lock create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/README.md.gotmpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/DEVELOPERS.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_affinity.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_agent-config.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_cluster.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_custom-attributes.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_dnsconfig.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_fedramp.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_hostnetwork.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_images.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_insights.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_insights_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_labels.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_license.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_license_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_low-data-mode.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_nodeselector.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_priority-class-name.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_privileged.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_proxy.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_security-context.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_serviceaccount.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_staging.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_tolerations.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_verbose-log.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/ci/test-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/static/lowdatamodedefaults.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/static/metrictyperelabeldefaults.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/templates/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/templates/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/templates/configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/templates/secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/templates/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/templates/statefulset.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/tests/configmap_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/tests/configurator_image_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/tests/integration_filters_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/tests/lowdatamode_configmap_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/Chart.lock create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/README.md.gotmpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/DEVELOPERS.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_affinity.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_agent-config.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_cluster.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_custom-attributes.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_dnsconfig.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_fedramp.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_hostnetwork.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_images.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_insights.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_insights_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_labels.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_license.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_license_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_low-data-mode.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_nodeselector.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_priority-class-name.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_privileged.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_proxy.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_security-context.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_serviceaccount.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_staging.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_tolerations.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_verbose-log.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/ci/test-bare-minimum-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/ci/test-custom-attributes-as-map.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/ci/test-custom-attributes-as-string.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/ci/test-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/templates/NOTES.txt create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/templates/_helpers_compatibility.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/templates/agent-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/templates/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/templates/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/templates/configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/templates/deployment.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/templates/secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/templates/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/tests/agent_configmap_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/tests/configmap_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/tests/deployment_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/tests/images_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/tests/security_context_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/Chart.lock create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/README.md.gotmpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/DEVELOPERS.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_affinity.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_agent-config.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_cluster.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_custom-attributes.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_dnsconfig.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_fedramp.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_hostnetwork.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_images.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_insights.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_insights_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_labels.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_license.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_license_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_low-data-mode.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_nodeselector.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_priority-class-name.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_privileged.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_proxy.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_security-context.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_serviceaccount.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_staging.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_tolerations.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_verbose-log.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/ci/test-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/templates/NOTES.txt create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/job-createSecret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/job-patchWebhook.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/psp.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/role.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/rolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/templates/admission-webhooks/mutatingWebhookConfiguration.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/templates/cert-manager.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/templates/deployment.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/templates/service.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/tests/cluster_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/tests/job_serviceaccount_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/tests/rbac_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/tests/volume_mounts_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/Chart.lock create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/README.md.gotmpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/DEVELOPERS.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_affinity.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_agent-config.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_cluster.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_custom-attributes.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_dnsconfig.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_fedramp.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_hostnetwork.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_images.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_insights.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_insights_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_labels.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_license.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_license_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_low-data-mode.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_nodeselector.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_priority-class-name.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_privileged.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_proxy.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_security-context.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_serviceaccount.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_staging.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_tolerations.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_verbose-log.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/ci/test-lowdatamode-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/ci/test-override-global-lowdatamode.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/ci/test-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/static/lowdatamodedefaults.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/templates/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/templates/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/templates/configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/templates/deployment.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/templates/secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/templates/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/tests/configmap_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/tests/deployment_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/tests/labels_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/pixie-operator-chart/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/pixie-operator-chart/crds/olm_crd.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/pixie-operator-chart/crds/vizier_crd.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/pixie-operator-chart/templates/00_olm.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/pixie-operator-chart/templates/01_px_olm.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/pixie-operator-chart/templates/02_catalog.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/pixie-operator-chart/templates/03_subscription.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/pixie-operator-chart/templates/04_vizier.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/pixie-operator-chart/templates/deleter.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/pixie-operator-chart/templates/deleter_role.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/charts/pixie-operator-chart/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/ci/test-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/questions.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.88/values.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.231/.helmignore create mode 100644 charts/speedscale/speedscale-operator/2.2.231/Chart.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.231/LICENSE create mode 100644 charts/speedscale/speedscale-operator/2.2.231/README.md create mode 100644 charts/speedscale/speedscale-operator/2.2.231/app-readme.md create mode 100644 charts/speedscale/speedscale-operator/2.2.231/questions.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.231/templates/NOTES.txt create mode 100644 charts/speedscale/speedscale-operator/2.2.231/templates/admission.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.231/templates/configmap.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.231/templates/crds/trafficreplays.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.231/templates/deployments.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.231/templates/hooks.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.231/templates/rbac.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.231/templates/secrets.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.231/templates/services.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.231/templates/tls.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.231/values.yaml create mode 100644 charts/traefik/traefik/30.0.2/.helmignore create mode 100644 charts/traefik/traefik/30.0.2/Changelog.md create mode 100644 charts/traefik/traefik/30.0.2/Chart.yaml create mode 100644 charts/traefik/traefik/30.0.2/EXAMPLES.md create mode 100644 charts/traefik/traefik/30.0.2/Guidelines.md create mode 100644 charts/traefik/traefik/30.0.2/LICENSE create mode 100644 charts/traefik/traefik/30.0.2/README.md create mode 100644 charts/traefik/traefik/30.0.2/VALUES.md create mode 100644 charts/traefik/traefik/30.0.2/app-readme.md create mode 100644 charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_backendtlspolicies.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_gatewayclasses.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_gateways.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_grpcroutes.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_httproutes.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_referencegrants.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_tcproutes.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_tlsroutes.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_udproutes.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/hub.traefik.io_accesscontrolpolicies.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apiaccesses.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apiportals.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apiratelimits.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apis.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apiversions.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/traefik.io_ingressroutes.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/traefik.io_ingressroutetcps.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/traefik.io_ingressrouteudps.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/traefik.io_middlewares.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/traefik.io_middlewaretcps.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/traefik.io_serverstransports.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/traefik.io_serverstransporttcps.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/traefik.io_tlsoptions.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/traefik.io_tlsstores.yaml create mode 100644 charts/traefik/traefik/30.0.2/crds/traefik.io_traefikservices.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/NOTES.txt create mode 100644 charts/traefik/traefik/30.0.2/templates/_helpers.tpl create mode 100644 charts/traefik/traefik/30.0.2/templates/_podtemplate.tpl create mode 100644 charts/traefik/traefik/30.0.2/templates/_service-metrics.tpl create mode 100644 charts/traefik/traefik/30.0.2/templates/_service.tpl create mode 100644 charts/traefik/traefik/30.0.2/templates/daemonset.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/deployment.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/extra-objects.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/gateway.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/gatewayclass.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/hpa.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/hub-admission-controller.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/hub-apiportal.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/ingressclass.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/ingressroute.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/poddisruptionbudget.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/prometheusrules.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/provider-file-cm.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/pvc.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/rbac/clusterrole.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/rbac/clusterrolebinding.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/rbac/podsecuritypolicy.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/rbac/role.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/rbac/rolebinding.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/rbac/serviceaccount.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/requirements.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/service-metrics.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/service.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/servicemonitor.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/tlsoption.yaml create mode 100644 charts/traefik/traefik/30.0.2/templates/tlsstore.yaml create mode 100644 charts/traefik/traefik/30.0.2/values.yaml diff --git a/assets/confluent/confluent-for-kubernetes-0.1033.3.tgz b/assets/confluent/confluent-for-kubernetes-0.1033.3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..dde31df385dfc4232b01f6310ab1dec7ca5eb66a GIT binary patch literal 369839 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
5Z-OH=p(ge353St;t}F$v?c4;rBtyTr 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

)D;J4iPnq#Js5-oqu^SDYN0^LnMZdp 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

  • c6$;-3J$?12KSC?-mZ?Df@|2^b9n1FM*T?#lu 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

    (l8hK?C}m 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%0D}u#Ff72!{n{Vpz*vt#^xFEghTq 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 literal 0 HcmV?d00001 diff --git a/assets/new-relic/nri-bundle-5.0.88.tgz b/assets/new-relic/nri-bundle-5.0.88.tgz new file mode 100644 index 0000000000000000000000000000000000000000..5e453fcca4405aae272254319e6cf46192dced19 GIT binary patch literal 341397 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

    Km$-3~$h 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 )};YNqf*Cno-@?M>+);db)POR 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%0Ajfa{@l0tDku=#|-s&JG2oJS(O_wX`6k?zj#i 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 mt4jJtXMK%I;;*3?Dc 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 b|9<{&@&6ys|MLS+LHIxU$FyAxUW^O?#qrTV literal 0 HcmV?d00001 diff --git a/assets/traefik/traefik-30.0.2.tgz b/assets/traefik/traefik-30.0.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..92a076e892ccdc6b5e1b466eb87d2a8b51e2815d GIT binary patch literal 222585 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.15-0' + catalog.cattle.io/release-name: confluent-for-kubernetes +apiVersion: v1 +appVersion: 2.9.0 +description: A Helm chart to deploy Confluent for Kubernetes +home: https://www.confluent.io/ +icon: file://assets/icons/confluent-for-kubernetes.png +keywords: +- Confluent +- Confluent Operator +- Confluent Platform +- CFK +kubeVersion: '>=1.15-0' +maintainers: +- email: operator@confluent.io + name: Confluent Operator +name: confluent-for-kubernetes +sources: +- https://docs.confluent.io/current/index.html +version: 0.1033.3 diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/README.md b/charts/confluent/confluent-for-kubernetes/0.1033.3/README.md new file mode 100644 index 000000000..512ca36c7 --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/README.md @@ -0,0 +1,72 @@ +Confluent for Kubernetes +================================================================== + +Confluent for Kubernetes (CFK) is a cloud-native control plane for deploying and managing Confluent in your private cloud environment. It provides standard and simple interface to customize, deploy, and manage Confluent Platform through declarative API. + +Confluent for Kubernetes runs on Kubernetes, the runtime for private cloud architectures. + + + + + + NOTE: Confluent for Kubernetes is the next generation of Confluent Operator. For Confluent Operator 1.x documentation, see [Confluent Operator 1](https://docs.confluent.io/operator/1.7.0/overview.html), or use the version picker to browse to a specific version of the documentation. + +See [Introducing Confluent for Kubernetes](https://www.confluent.io/blog/confluent-for-kubernetes-offers-cloud-native-kafka-automation/) for an overview. + +The following shows the high-level architecture of Confluent for Kubernetes and Confluent Platform in Kubernetes. + +[![_images/co-architecture.png](https://docs.confluent.io/operator/current/_images/co-architecture.png)](_images/co-architecture.png) + +Features +--------------------------------------------------- + +The following are summaries of the main, notable features of Confluent for Kubernetes. + +#### Cloud Native Declarative API + +* Declarative Kubernetes-native API approach to configure, deploy, and manage Confluent Platform components (Apache KafkaB., Connect workers, ksqlDB, Schema Registry, Confluent Control Center) and resources (topics, rolebindings) through Infrastructure as Code (IaC). +* Provides built-in automation for cloud-native security best practices: + * Complete granular RBAC, authentication and TLS network encryption + * Auto-generated certificates + * Support for credential management systems, such as Hashicorp Vault, to inject sensitive configurations in memory to Confluent deployments +* Provides server properties, JVM, and Log4j configuration overrides for customization of all Confluent Platform components. + +#### Upgrades + +* Provides automated rolling updates for configuration changes. +* Provides automated rolling upgrades with no impact to Kafka availability. + +#### Scaling + +* Provides single command, automated scaling and reliability checks of Confluent Platform. + +#### Resiliency + +* Restores a Kafka pod with the same Kafka broker ID, configuration, and persistent storage volumes if a failure occurs. +* Provides automated rack awareness to spread replicas of a partition across different racks (or zones), improving availability of Kafka brokers and limiting the risk of data loss. + +#### Scheduling + +* Supports Kubernetes labels and annotations to provide useful context to DevOps teams and ecosystem tooling. +* Supports Kubernetes tolerations and pod/node affinity for efficient resource utilization and pod placement. + +#### Monitoring + +* Supports metrics aggregation using JMX/Jolokia. +* Supports aggregated metrics export to Prometheus. + +Licensing +----------------------------------------------------- + +You can use Confluent for Kubernetes and Confluent Control Center for a 30-day trial period without a license key. + +After 30 days, Confluent for Kubernetes and Control Center require a license key. Confluent issues keys to subscribers, along with providing [enterprise-level support](https://www.confluent.io/subscription/) for Confluent components and Confluent for Kubernetes. + +If you are a subscriber, contact Confluent Support at [support@confluent.io](mailto:support@confluent.io) for more information. + +See [Update Confluent Platform License](co-license.html#co-license-key) if you have received a key for Confluent for Kubernetes. + +© Copyright 2021 , Confluent, Inc. [Privacy Policy](https://www.confluent.io/confluent-privacy-statement/) | [Terms & Conditions](https://www.confluent.io/terms-of-use/). Apache, Apache Kafka, Kafka and the Kafka logo are trademarks of the [Apache Software Foundation](http://www.apache.org/). All other trademarks, servicemarks, and copyrights are the property of their respective owners. + +[Please report any inaccuracies on this page or suggest an edit.](mailto:docs@confluent.io) + diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/app-readme.md b/charts/confluent/confluent-for-kubernetes/0.1033.3/app-readme.md new file mode 100644 index 000000000..cfcabdd21 --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/app-readme.md @@ -0,0 +1,3 @@ +##Confluent For Kubernetes + +With Confluent for Kubernetes, Confluent brings a cloud-native experience for data in motion workloads in on-premises environments. Based on our expertise and learnings from operating over 5,000 clusters in Confluent Cloud, Confluent for Kubernetes offers an opinionated deployment of Confluent Platform that enhances the platformb's elasticity, ease of operations, and resiliency. diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_clusterlinks.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_clusterlinks.yaml new file mode 100644 index 000000000..5c8a627c3 --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_clusterlinks.yaml @@ -0,0 +1,883 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: clusterlinks.platform.confluent.io +spec: + group: platform.confluent.io + names: + categories: + - all + - confluent-platform + - confluent + kind: ClusterLink + listKind: ClusterLinkList + plural: clusterlinks + shortNames: + - cl + - clusterlink + - clink + singular: clusterlink + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.clusterLinkID + name: ID + type: string + - jsonPath: .status.state + name: Status + type: string + - jsonPath: .status.destinationKafkaClusterID + name: DestClusterID + type: string + - jsonPath: .status.sourceKafkaClusterID + name: SrcClusterID + type: string + - jsonPath: .status.numMirrorTopics + name: MirrorTopicCount + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: ClusterLink is the schema for the ClusterLink 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: spec defines the desired state of the cluster link. + properties: + aclFilters: + description: |- + aclFilters specify the list of ACLs to be migrated from the source cluster to the + destination cluster. + items: + description: |- + AclFilter defines the configuration for the ACLs filter. This follows the same pattern as defined in the + cluster linking documentation. More info: + https://docs.confluent.io/platform/current/multi-dc-deployments/cluster-linking/security.html#cluster-link-acls-migrate + properties: + accessFilter: + description: AclSyncAccessFilter defines the access filter for + ACLs. + properties: + host: + description: |- + host is the host for which operations can be coming from. + The default value is `*` that matches all hosts. + type: string + operation: + description: |- + operation specifies the operation type of the filter. It can be `ANY` or operations + based on resource type defined in the following Confluent documentation: + https://docs.confluent.io/platform/current/kafka/authorization.html#acl-operations + type: string + permissionType: + description: permissionType is the permission type of the + filter. Valid options are `any`, `allow`, and `deny`. + enum: + - any + - allow + - deny + type: string + principal: + description: |- + principal is the name of the principal. + The default value is `*`. + type: string + required: + - operation + - permissionType + type: object + resourceFilter: + description: AclSyncResourceFilter specifies the resource filter + for ACLs. + properties: + name: + description: |- + name is the name of the resource associated with this filter. + The default value is `*`. + type: string + patternType: + description: patternType is the pattern of the resource. + Valid options are `prefixed`, `literal`, `any`, and `match`. + enum: + - prefixed + - literal + - any + - match + type: string + resourceType: + description: resourceType is the type of the filter. Valid + options are `any`, `cluster`, `group`, `topic`, `transactionId`, + and `delegationToken`. + enum: + - any + - cluster + - group + - topic + - transcationId + - delegationToken + type: string + required: + - patternType + - resourceType + type: object + required: + - accessFilter + - resourceFilter + type: object + type: array + configs: + additionalProperties: + type: string + description: |- + configs is a map of string key and value pairs. It specifies additional configurations for the cluster link. + More info: https://docs.confluent.io/platform/current/multi-dc-deployments/cluster-linking/configs.html + type: object + x-kubernetes-map-type: granular + consumerGroupFilters: + description: |- + consumerGroupFilters specify a list of consumer groups to be migrated from + the source cluster to the destination cluster. + items: + description: ClusterLinkOptionsFilter defines the scheme for a filter + properties: + filterType: + description: filterType specifies the filter type. Valid options + are `INCLUDE` and `EXCLUDE`. + enum: + - INCLUDE + - EXCLUDE + type: string + name: + description: name is the resource name associated with this + filter. + type: string + patternType: + description: patternType is the pattern of the resource. Valid + options are `PREFIXED` and `LITERAL`. + enum: + - PREFIXED + - LITERAL + type: string + required: + - filterType + - name + - patternType + type: object + type: array + destinationKafkaCluster: + description: destinationKafkaCluster specifies the destination Kafka + cluster and its REST API configuration. + properties: + authentication: + description: authentication specifies the authentication for the + Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side JaaS + configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another way to + provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim in + the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected issuer + in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of claim + in token for identifying the groups of subject in the + JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout with + IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry backoff + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff with + IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim in JWT + to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: |- + bootstrapEndpoint specifies the bootstrap endpoint for the Kafka cluster. + When `spec.sourceInitiatedLink.linkMode` is configured as `Source`, this is required for + `spec.destinationKafkaCluster` and not required for `spec.sourceKafkaCluster`. + For other cluster links this is required for `spec.sourceKafkaCluster` and not required for + `spec.destinationKafkaCluster`. + minLength: 1 + pattern: .+:[0-9]+ + type: string + clusterID: + description: |- + clusterID specifies the id of the Kafka cluster. + If clusterID is defined for the Kafka cluster, it takes precedence over using the REST API + for getting the cluster ID. + minLength: 1 + type: string + kafkaRestClassRef: + description: |- + kafkaRestClassRef references the KafkaRestClass application resource which + defines the Kafka REST API connection information. + When `spec.sourceInitiatedLink.linkMode` is configured as `Source`, this is required for + `spec.sourceKafkaCluster` and optional for `spec.destinationKafkaCluster` if `spec.clusterID` is set. + For other cluster links this is required for 'spec.destinationKafkaCluster` and optional for + `spec.sourceKafkaCluster` if the `spec.clusterID` is set. + properties: + name: + description: name specifies the name of the KafkaRestClass + application resource. + minLength: 1 + type: string + namespace: + description: namespace specifies the namespace of the KafkaRestClass. + type: string + required: + - name + type: object + tls: + description: tls specifies the client-side TLS configuration for + the Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer contains the directory path in the container where + `fullchain.pem`, `privkey.pem`, `cacerts.pem` or `tls.crt`, `tls.key`, `ca.crt` keys are mounted. + minLength: 1 + type: string + enabled: + description: enabled specifies whether to enable the TLS configuration + for the cluster link. The default value is `false`. + type: boolean + keyPassword: + description: |- + keyPassword references the secret containing the SSL key password if the private key passed + in the secretRef above is encrypted. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + mirrorTopicOptions: + description: mirrorTopicOptions specify configuration options for + mirror topics. + properties: + autoCreateTopics: + description: |- + autoCreateTopics specifies configurations for the cluster link to + automatically create mirror topics on the destination cluster for topics that exist on the source cluster based on defined filters. + More info: https://docs.confluent.io/platform/current/multi-dc-deployments/cluster-linking/mirror-topics-cp.html#auto-create-mirror-topics + properties: + enabled: + description: |- + enabled specifies whether to auto-create mirror topics based on topics on the source cluster. + When set to “true”, mirror topics will be auto-created. Setting this option to “false” disables mirror topic creation and clears any existing filters. + type: boolean + topicFilters: + description: topicFilter contains an array of filters to apply + to indicate which topics should be mirrored. + items: + description: ClusterLinkOptionsFilter defines the scheme + for a filter + properties: + filterType: + description: filterType specifies the filter type. Valid + options are `INCLUDE` and `EXCLUDE`. + enum: + - INCLUDE + - EXCLUDE + type: string + name: + description: name is the resource name associated with + this filter. + type: string + patternType: + description: patternType is the pattern of the resource. + Valid options are `PREFIXED` and `LITERAL`. + enum: + - PREFIXED + - LITERAL + type: string + required: + - filterType + - name + - patternType + type: object + type: array + type: object + prefix: + description: |- + prefix specifies prefix for the mirror topics of the cluster link. + If configured, the valid mirror topic name should be defined with `` format + which mirrors the topic name of the format `` from source cluster. + When auto-create is enabled and the prefix is configured then the topics created on the destination will automatically contain the prefix. + Otherwise, `spec.mirrorTopic.name` should be defined with `` format. + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9\._\-]*$ + type: string + type: object + mirrorTopics: + description: mirrorTopics specify the mirror topics under this cluster + link. + items: + description: MirrorTopic defines the mirror topic configuration. + properties: + configs: + additionalProperties: + type: string + description: configs is a map of string key and value pairs. + It specifies any additional configuration or configuration + overrides for the mirror topic. + type: object + x-kubernetes-map-type: granular + name: + description: |- + name is the mirror topic name. If the sourceTopicName is not configured, + we assume that the sourceTopicName is the same as mirrorTopicName, + so a topic with the exact same name must exist on the source cluster and + no topic with this name should exist on the destination cluster. + When `spec.mirrorTopicOptions.prefix: ` is configured for the cluster link, + the name has to be of the format ``. + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9\._\-]*$ + type: string + replicationFactor: + description: |- + replicationFactor specifies the replication factor for the mirror topic on the destination cluster. + If this is not configured, mirror topic will inherit the broker `default.replication.factor` configuration. + format: int32 + type: integer + sourceTopicName: + description: |- + sourceTopicName is topic name on the source cluster that will be mirrored to the destination cluster. + When `spec.mirrorTopicOptions.prefix: ` is not configured, you should not configure this field. + If it is configured, a topic with the exact same name must exist on the source cluster. + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9\._\-]*$ + type: string + state: + description: |- + state specifies the desired state for this mirror topic. Valid options are + `ACTIVE`, `FAILOVER`, `PAUSE`, and `PROMOTE`. The default value is `ACTIVE`. + enum: + - PAUSE + - PROMOTE + - FAILOVER + - ACTIVE + type: string + required: + - name + type: object + type: array + name: + description: |- + name specifies the cluster link name. If not configured, then ClusterLink CR name is used + as the cluster link name. + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9\._\-]*$ + type: string + sourceInitiatedLink: + description: sourceInitiatedLink specify configs for source initiated + cluster links. + properties: + linkMode: + description: linkMode specifies if this source initiated cluster + link is in Source or Destination mode. + enum: + - Source + - Destination + - Bidirectional + type: string + required: + - linkMode + type: object + sourceKafkaCluster: + description: sourceKafkaCluster specifies the source Kafka cluster + and its REST API configuration. + properties: + authentication: + description: authentication specifies the authentication for the + Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side JaaS + configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another way to + provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim in + the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected issuer + in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of claim + in token for identifying the groups of subject in the + JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout with + IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry backoff + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff with + IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim in JWT + to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: |- + bootstrapEndpoint specifies the bootstrap endpoint for the Kafka cluster. + When `spec.sourceInitiatedLink.linkMode` is configured as `Source`, this is required for + `spec.destinationKafkaCluster` and not required for `spec.sourceKafkaCluster`. + For other cluster links this is required for `spec.sourceKafkaCluster` and not required for + `spec.destinationKafkaCluster`. + minLength: 1 + pattern: .+:[0-9]+ + type: string + clusterID: + description: |- + clusterID specifies the id of the Kafka cluster. + If clusterID is defined for the Kafka cluster, it takes precedence over using the REST API + for getting the cluster ID. + minLength: 1 + type: string + kafkaRestClassRef: + description: |- + kafkaRestClassRef references the KafkaRestClass application resource which + defines the Kafka REST API connection information. + When `spec.sourceInitiatedLink.linkMode` is configured as `Source`, this is required for + `spec.sourceKafkaCluster` and optional for `spec.destinationKafkaCluster` if `spec.clusterID` is set. + For other cluster links this is required for 'spec.destinationKafkaCluster` and optional for + `spec.sourceKafkaCluster` if the `spec.clusterID` is set. + properties: + name: + description: name specifies the name of the KafkaRestClass + application resource. + minLength: 1 + type: string + namespace: + description: namespace specifies the namespace of the KafkaRestClass. + type: string + required: + - name + type: object + tls: + description: tls specifies the client-side TLS configuration for + the Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer contains the directory path in the container where + `fullchain.pem`, `privkey.pem`, `cacerts.pem` or `tls.crt`, `tls.key`, `ca.crt` keys are mounted. + minLength: 1 + type: string + enabled: + description: enabled specifies whether to enable the TLS configuration + for the cluster link. The default value is `false`. + type: boolean + keyPassword: + description: |- + keyPassword references the secret containing the SSL key password if the private key passed + in the secretRef above is encrypted. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + required: + - destinationKafkaCluster + - sourceKafkaCluster + type: object + status: + description: status defines the observed state of the cluster link. + properties: + appState: + default: Unknown + description: appState is the current state of the cluster link application. + enum: + - Unknown + - Created + - Failed + - Deleted + type: string + clusterLinkID: + description: clusterLinkID is the id of the cluster link. + type: string + clusterLinkName: + description: clusterLinkName is the name of the cluster link. + type: string + conditions: + description: conditions are the latest available observations of the + cluster link's state. + items: + description: Condition represent the latest available observations + of the current state. + properties: + lastProbeTime: + description: lastProbeTime shows the last time the condition + was evaluated. + format: date-time + type: string + lastTransitionTime: + description: lastTransitionTime shows the last time the condition + was transitioned from one status to another. + format: date-time + type: string + message: + description: message shows a human-readable message with details + about the transition. + type: string + reason: + description: reason shows the reason for the last transition + of the condition. + type: string + status: + description: status shows the status of the condition, one of + `True`, `False`, or `Unknown`. + type: string + type: + description: type shows the condition type. + type: string + type: object + type: array + destinationKafkaClusterID: + description: destinationKafkaClusterID is the ID of the destination + Kafka cluster. + type: string + kafkaCluster: + description: 'kafkaCluster is the Kafka cluster this cluster link + belongs to. The format is: `/`' + type: string + mirrorTopics: + additionalProperties: + description: |- + MirrorTopicStatus specifies the status reported for each mirror topic as part of + the cluster link status. + properties: + observedGeneration: + description: observedGeneration is the most recent generation + observed for this Confluent component. + format: int64 + type: integer + replicationFactor: + description: replicationFactor specifies the replication factor + for the mirror topic on the destination cluster. + format: int32 + type: integer + sourceTopicName: + description: sourceTopicName is the name of the topic being + mirrored on the source cluster. + type: string + status: + description: |- + status is the status of the mirror topic. + It can be `ACTIVE`, `FAILED`, `PAUSED`, `STOPPED`, and `PENDING_STOPPED`. + type: string + type: object + description: mirrorTopics is a map of mirror topic name to its status + type: object + x-kubernetes-map-type: granular + numMirrorTopics: + description: numMirrorTopics is the number of mirror topics for the + cluster link. + type: integer + observedGeneration: + description: observedGeneration is the most recent generation observed + for this Confluent component. + format: int64 + type: integer + sourceKafkaClusterID: + description: sourceKafkaClusterID is the ID of the source Kafka cluster. + type: string + state: + description: state is the current state of the cluster link. + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_confluentrolebindings.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_confluentrolebindings.yaml new file mode 100644 index 000000000..8ff4d8c9e --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_confluentrolebindings.yaml @@ -0,0 +1,296 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: confluentrolebindings.platform.confluent.io +spec: + group: platform.confluent.io + names: + categories: + - all + - confluent-platform + - confluent + kind: ConfluentRolebinding + listKind: ConfluentRolebindingList + plural: confluentrolebindings + shortNames: + - cfrb + - confluentrolebinding + singular: confluentrolebinding + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.state + name: Status + type: string + - jsonPath: .status.kafkaClusterID + name: KafkaClusterID + type: string + - jsonPath: .status.principal + name: Principal + type: string + - jsonPath: .status.role + name: Role + type: string + - jsonPath: .status.kafkaRestClass + name: KafkaRestClass + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.kafkaCluster + name: KafkaCluster + priority: 1 + type: string + - jsonPath: .status.clusterRegistryName + name: ClusterRegistryName + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: ConfluentRolebinding is the schema for the ConfluentRolebinding + 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: spec defines the desired state of the ConfluentRolebinding. + properties: + clustersScopeByIds: + description: clustersScopeByIds specify the scope of the Confluent + component cluster(s) via cluster id(s). + properties: + connectClusterId: + description: connectClusterId specifies the Connect cluster id. + minLength: 1 + type: string + kafkaClusterId: + description: kafkaClusterId specifies the id of the Kafka cluster + id. + minLength: 1 + type: string + ksqlClusterId: + description: ksqlClusterId specifies the ksqlDB cluster id. + minLength: 1 + type: string + schemaRegistryClusterId: + description: schemaRegistryClusterId specifies the Schema Registry + cluster id. + minLength: 1 + type: string + type: object + clustersScopeByRegistryName: + description: clustersScopeByRegistryName specifies the unique cluster + name you registered in the cluster registry. + minLength: 1 + type: string + kafkaRestClassRef: + description: kafkaRestClassRef references the KafkaRestClass that + defines the Kafka REST API connection information. + properties: + name: + description: name specifies the name of the KafkaRestClass application + resource. + minLength: 1 + type: string + namespace: + description: namespace specifies the namespace of the KafkaRestClass. + type: string + required: + - name + type: object + principal: + description: RolebindingPrincipal defines the principal(user/group) + the rolebinding belongs to. + properties: + name: + description: name specifies the name of the principal. + minLength: 1 + type: string + type: + description: type specifies the type of the principal. Valid options + are `user` and `group`. + enum: + - user + - group + type: string + required: + - name + - type + type: object + resourcePatterns: + description: resourcePatterns specify the qualified resources associated + with this rolebinding. + items: + description: ResourcePattern specifies the qualified resource info + associated with this rolebinding. + properties: + name: + description: name specifies the name of the resource associated + with this rolebinding. + minLength: 1 + type: string + patternType: + description: |- + patternType specifies the pattern of the resource. Valid options are + `PREFIXED` or `LITERAL`. The default value is `LITERAL`. + enum: + - PREFIXED + - LITERAL + type: string + resourceType: + description: |- + resourceType refers to the type of the resource. + Valid options are `Topic`, `Group`, `Subject`, `KsqlCluster`, `Cluster`, `TransactionalId`, etc. + minLength: 1 + type: string + required: + - name + - resourceType + type: object + type: array + role: + description: role specifies the name of the role. + minLength: 1 + type: string + required: + - principal + - role + type: object + status: + description: status is the observed state of the ConfluentRolebinding. + properties: + appState: + default: Unknown + description: appState is the current state of the rolebinding application. + enum: + - Unknown + - Created + - Failed + - Deleted + type: string + clusterRegistryName: + description: clusterRegistryName is the cluster registry name the + rolebinding associated with. + type: string + conditions: + description: conditions are the latest available observations of the + rolebinding's state. + items: + description: Condition represent the latest available observations + of the current state. + properties: + lastProbeTime: + description: lastProbeTime shows the last time the condition + was evaluated. + format: date-time + type: string + lastTransitionTime: + description: lastTransitionTime shows the last time the condition + was transitioned from one status to another. + format: date-time + type: string + message: + description: message shows a human-readable message with details + about the transition. + type: string + reason: + description: reason shows the reason for the last transition + of the condition. + type: string + status: + description: status shows the status of the condition, one of + `True`, `False`, or `Unknown`. + type: string + type: + description: type shows the condition type. + type: string + type: object + type: array + kafkaCluster: + description: 'kafkaCluster is the Kafka cluster the rolebinding belongs + to. The format is: `/`.' + type: string + kafkaClusterID: + description: kafkaClusterID is the id of the Kafka cluster. + type: string + kafkaRestClass: + description: 'kafkaRestClass is the kafkaRestClass this rolebinding + uses. The format is: `/`.' + type: string + mdsEndpoint: + description: mdsEndpoint is the MDS endpoint. + type: string + observedGeneration: + description: observedGeneration is the most recent generation observed + for this Confluent component. + format: int64 + type: integer + principal: + description: 'principal is the principal the rolebinding belongs to. + The format is: `:`.' + type: string + resourcePatterns: + description: resourcePatterns are the resource patterns this rolebinding + is associated with. + items: + description: ResourcePattern specifies the qualified resource info + associated with this rolebinding. + properties: + name: + description: name specifies the name of the resource associated + with this rolebinding. + minLength: 1 + type: string + patternType: + description: |- + patternType specifies the pattern of the resource. Valid options are + `PREFIXED` or `LITERAL`. The default value is `LITERAL`. + enum: + - PREFIXED + - LITERAL + type: string + resourceType: + description: |- + resourceType refers to the type of the resource. + Valid options are `Topic`, `Group`, `Subject`, `KsqlCluster`, `Cluster`, `TransactionalId`, etc. + minLength: 1 + type: string + required: + - name + - resourceType + type: object + type: array + role: + description: role is the role this rolebinding is associated with. + type: string + state: + description: state is the state of this rolebinding. + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_connectors.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_connectors.yaml new file mode 100644 index 000000000..07bf6d1bb --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_connectors.yaml @@ -0,0 +1,496 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: connectors.platform.confluent.io +spec: + group: platform.confluent.io + names: + categories: + - all + - confluent-platform + - confluent + kind: Connector + listKind: ConnectorList + plural: connectors + shortNames: + - ctr + - connector + singular: connector + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.state + name: Status + type: string + - jsonPath: .status.connectorState + name: ConnectorStatus + type: string + - jsonPath: .status.tasksReady + name: Tasks-Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.connectRestEndpoint + name: ConnectEndpoint + priority: 1 + type: string + - jsonPath: .status.failedTasksCount + name: Tasks-Failed + priority: 1 + type: string + - jsonPath: .status.workerID + name: WorkerID + priority: 1 + type: string + - jsonPath: .status.restartPolicy.type + name: RestartPolicy + priority: 1 + type: string + - jsonPath: .status.kafkaClusterID + name: KafkaClusterID + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: Connector is the schema for the Connector 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: spec defines the desired state of the Connector. + properties: + class: + description: |- + class specifies the class name of the connector. + The Connect cluster displays the supported class names in its status. + minLength: 1 + type: string + configs: + additionalProperties: + type: string + description: configs is a map of string key and value pairs. It specifies + the additional configurations for the connector. + type: object + x-kubernetes-map-type: granular + connectClusterRef: + description: connectClusterRef references the CFK managed Connect + cluster. + properties: + name: + description: name specifies the name of the Confluent Platform + component cluster. + type: string + namespace: + description: namespace specifies the namespace where the Confluent + Platform component cluster is running. + type: string + required: + - name + type: object + connectRest: + description: connectRest specifies the Connect REST API connection + configuration. + properties: + authentication: + description: authentication specifies the REST API authentication + mechanism. + properties: + basic: + description: basic specifies the basic authentication settings + for the REST API client. + properties: + debug: + description: debug enables the basic authentication debug + logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + bearer: + description: bearer specifies the bearer authentication settings + for the REST API client. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: oauth specifies the OAuth authentication settings + for the REST API client. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass the + basic credential through a directory path in the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to pass + the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the REST API authentication type. + Valid options are `basic`, `bearer`, `mtls` and `oauth`. + enum: + - basic + - bearer + - mtls + - oauth + type: string + required: + - type + type: object + endpoint: + description: endpoint specifies where Confluent REST API is running. + minLength: 1 + pattern: ^https?://.* + type: string + kafkaClusterID: + description: |- + kafkaClusterID specifies the id of Kafka cluster. + It takes precedence over using the Kafka REST API to get the cluster id. + minLength: 1 + type: string + tls: + description: "tls specifies the custom TLS structure for the application + resources,\n\t// e.g. connector, topic, schema, of the Confluent + Platform components.\n\t// +optional" + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer contains the directory path in the container where + `keystore.jks`, `truststore.jks`, `jksPassword.txt` keys are mounted. + minLength: 1 + type: string + jksPassword: + description: jksPassword specifies the secret name that contains + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef specifies the secret name that contains the certificates. + More info about certificates key/value format: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: object + name: + description: |- + name specifies the connector name. If not configured, + the Connector CR name is used as the connector name. + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9\._\-]*$ + type: string + restartPolicy: + description: restartPolicy specifies the policy to restart failed + tasks of the connector. + properties: + maxRetry: + description: maxRetry specifies the max number of tries to restart + failed tasks when the `restartPolicy` type is `OnFailure`. The + default value is `10`. + format: int32 + minimum: 1 + type: integer + type: + description: |- + type specifies the policy type to restart connector tasks. Valid options are `OnFailure` and `Never`. + Default value is `OnFailure`, which means it will restart automatically when a task fails if the `maxRetry` value is not reached. + enum: + - OnFailure + - Never + type: string + required: + - type + type: object + taskMax: + description: |- + taskMax specifies the maximum number of tasks for the connector. It must be greater than 0. + The connector may create fewer tasks if it cannot achieve this level of parallelism. + format: int32 + minimum: 1 + type: integer + required: + - class + - taskMax + type: object + status: + description: status defines the observed state of the Connector. + properties: + appState: + default: Unknown + description: appState is the current state of the connector application. + enum: + - Unknown + - Created + - Failed + - Deleted + type: string + conditions: + description: conditions are the latest available observations of the + connector state. + items: + description: Condition represent the latest available observations + of the current state. + properties: + lastProbeTime: + description: lastProbeTime shows the last time the condition + was evaluated. + format: date-time + type: string + lastTransitionTime: + description: lastTransitionTime shows the last time the condition + was transitioned from one status to another. + format: date-time + type: string + message: + description: message shows a human-readable message with details + about the transition. + type: string + reason: + description: reason shows the reason for the last transition + of the condition. + type: string + status: + description: status shows the status of the condition, one of + `True`, `False`, or `Unknown`. + type: string + type: + description: type shows the condition type. + type: string + type: object + type: array + connectRestEndpoint: + description: connectRestEndpoint is the REST endpoint of the Connect + cluster. + type: string + connectorState: + description: connectorState is the status of the connector instance. + type: string + failedTasks: + additionalProperties: + description: TaskStatus defines the connector task status. + properties: + id: + description: Id is the id of the task. + format: int32 + type: integer + retryCount: + description: retryCount is the number of retry attempts to restart + the failed task. + format: int32 + type: integer + workerID: + description: workerID is the workerId for the task. + type: string + required: + - id + type: object + description: |- + failedTasks is the map of connector tasks in the `FAILED` state. + Error messages of failed tasks are logged in the CFK logs as `INFO`. + You can also get the error message via Connect REST API calls. + type: object + x-kubernetes-map-type: granular + failedTasksCount: + description: failedTasksCount is the number of failed tasks. + format: int32 + type: integer + kafkaClusterID: + description: kafkaClusterID is the Kafka cluster id the connector + belongs to. + type: string + observedGeneration: + description: observedGeneration is the most recent generation observed + for this Confluent component. + format: int64 + type: integer + restartPolicy: + description: restartPolicy is the policy to restart failed tasks of + the connector. + properties: + maxRetry: + description: maxRetry specifies the max number of tries to restart + failed tasks when the `restartPolicy` type is `OnFailure`. The + default value is `10`. + format: int32 + minimum: 1 + type: integer + type: + description: |- + type specifies the policy type to restart connector tasks. Valid options are `OnFailure` and `Never`. + Default value is `OnFailure`, which means it will restart automatically when a task fails if the `maxRetry` value is not reached. + enum: + - OnFailure + - Never + type: string + required: + - type + type: object + state: + description: state is the custom resource state of the connector. + This is not the connector state, which can be `CREATED`, `ERROR`, + etc. + type: string + tasksReady: + description: |- + tasksReady is the number of running tasks based on `taskMax`. + The value is in the following format: `/` + type: string + trace: + description: trace is the error trace message for the connector instance. + type: string + workerID: + description: workerID is the workerId of the connector instance. + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_connects.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_connects.yaml new file mode 100644 index 000000000..663863d85 --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_connects.yaml @@ -0,0 +1,6941 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: connects.platform.confluent.io +spec: + group: platform.confluent.io + names: + categories: + - all + - confluent-platform + - confluent + kind: Connect + listKind: ConnectList + plural: connects + shortNames: + - connect + singular: connect + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.replicas + name: Replicas + type: string + - jsonPath: .status.readyReplicas + name: Ready + type: string + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.kafka.bootstrapEndpoint + name: Kafka + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: Connect is the schema for the Connect 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: spec defines the desired state of the Connect cluster. + properties: + authentication: + description: authentication specifies authentication configuration. + properties: + basic: + description: basic specifies the configuration for basic authentication. + properties: + debug: + description: debug enables the basic authentication debug + logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: OAuth specifies the configuration for OAuth authentication. + properties: + configuration: + description: configuration specifies the OAuth server settings. + properties: + audience: + description: audience specifies the audience claim in + the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected issuer + in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of claim + in token for identifying the groups of subject in the + JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout with + IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry backoff + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff with + IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim in JWT + to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass the basic + credential through a directory path in the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to pass the + required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication scheme for the + REST API server. Valid options are `basic` and `mtls`. + enum: + - basic + - mtls + - oauth + type: string + required: + - type + type: object + authorization: + description: authorization specifies the authorization configuration. + properties: + kafkaRestClassRef: + description: |- + kafkaRestClassRef references the KafkaRestClass + which specifies the Kafka REST API connection configuration. + properties: + name: + description: name specifies the name of the KafkaRestClass + application resource. + minLength: 1 + type: string + namespace: + description: namespace specifies the namespace of the KafkaRestClass. + type: string + required: + - name + type: object + type: + description: type specifies the client-side authorization type. + The valid option is `rbac`. + enum: + - rbac + type: string + required: + - type + type: object + build: + description: build defines the build configurations for connector + plugins. + properties: + onDemand: + description: OnDemand defines the build configurations for the + `onDemand` build type. + properties: + plugins: + description: plugins define the installation information for + connector plugins. + properties: + confluentHub: + description: confluentHub contains a list of connector + plugins you get from Confluent Hub. + items: + description: ConfluentHubPlugin contains the required + information to get the connector plugin from Confluent + Hub. + properties: + name: + description: name specifies the name of the connector + plugin. + minLength: 1 + type: string + owner: + description: owner specifies the individual or organization + that provides the connector plugin, for example, + `confluentinc`. + minLength: 1 + type: string + version: + description: version specifies the version of the + connector plugin, which can be either the version + of the plugin or the literal `latest`. + minLength: 1 + type: string + required: + - name + - owner + - version + type: object + type: array + locationType: + description: This field is deprecated and will be ignored + if set. + enum: + - confluentHub + - url + type: string + url: + description: url contains a list of URL plugins you get + from external URLs. + items: + description: URLPlugin defines the information to get + the connector plugin from an external URL. + properties: + archivePath: + description: |- + archivePath specifies the archive path of the connector plugin. + Currently, only support ZIP archives. + minLength: 1 + pattern: ^https?://.* + type: string + checksum: + description: |- + checksum defines the sha512sum checksum of the connector plugin's remote file. + It is used to verify the remote file after it is downloaded. + type: string + name: + description: name specifies the connector plugin + name. + minLength: 1 + type: string + required: + - archivePath + - checksum + - name + type: object + type: array + type: object + storageLimit: + anyOf: + - type: integer + - type: string + description: storageLimit specifies the max amount of node + volume that can be used to store connector plugins. The + default value is `4G`. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - plugins + type: object + type: + description: type specifies the build type for connector plugins. + Currently only the `onDemand` type is supported. + enum: + - onDemand + type: string + required: + - type + type: object + configOverrides: + description: configOverrides specifies the configs to override the + server, JVM, Log4j properties for the Connect cluster. + properties: + jvm: + description: |- + jvm is a list of JVM configuration supported by the Confluent Platform component. + This will either add or update the existing configuration. + items: + type: string + type: array + log4j: + description: |- + log4j is a list of Log4J configuration supported by the Confluent Platform component. + This will either add or update the existing configuration. + items: + type: string + type: array + server: + description: |- + server is a list of server configuration supported by the Confluent Platform component. + This will either add or update existing configuration. + items: + type: string + type: array + type: object + connectorOverridePolicy: + description: |- + connectorOverridePolicy allows the policy to permit per-connector override configuration + for producer/consumer/admin prefix. + More info: https://docs.confluent.io/platform/current/connect/security.html#separate-principals + enum: + - All + - Principal + type: string + connectorTLSCerts: + description: |- + connectorTLSCerts are the custom TLS certificates injected into the Connect cluster for connectors to use. + Check the Connect status for the mount path of the certificates. + A change will roll the cluster. + items: + description: MountedCustomTLSCertificate defines the mounted custom + TLS structure for the Confluent Platform component. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer contains the directory path in the container where + `keystore.jks`, `truststore.jks`, `jksPassword.txt` keys are mounted. + minLength: 1 + type: string + jksPassword: + description: jksPassword specifies the secret name that contains + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef specifies the secret name that contains the certificates. + More info about certificates key/value format: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: array + dependencies: + description: ConnectDependencies contains the dependencies the Connect + requires or can enable. + properties: + admin: + description: |- + admin contains the security configuration to connect to the admin client. + If `bootstrapEndpoint` is not configured, the security is configured based on the Kafka dependency configuration. + Configure this property if different bootstrap endpoint is required for the admin client. + properties: + authentication: + description: authentication defines the authentication for + the Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another way + to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap + endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + discovery: + description: discovery specifies the capability to discover + the Kafka cluster. + properties: + name: + description: name is the name of the Confluent Platform + component cluster. + type: string + namespace: + description: |- + namespace is where the Confluent Platform component is running. + The default value is the namespace where CFK is running. + type: string + secretRef: + description: secretRef is the name of the secret used + to discover the Confluent Platform component. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + tls: + description: tls defines the client-side TLS setting for the + Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + consumer: + description: |- + consumer contains the security configuration to connect to the Kafka cluster. It is used for sink connectors. + If `bootstrapEndpoint` is not configured, the security is configured based on the Kafka dependency configuration. + Configure this property if different bootstrap endpoint is required for the consumer. + properties: + authentication: + description: authentication defines the authentication for + the Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another way + to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap + endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + discovery: + description: discovery specifies the capability to discover + the Kafka cluster. + properties: + name: + description: name is the name of the Confluent Platform + component cluster. + type: string + namespace: + description: |- + namespace is where the Confluent Platform component is running. + The default value is the namespace where CFK is running. + type: string + secretRef: + description: secretRef is the name of the secret used + to discover the Confluent Platform component. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + tls: + description: tls defines the client-side TLS setting for the + Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + interceptor: + description: interceptor contains the dependency configuration + for the monitoring interceptor. + properties: + configs: + description: |- + configs describe the configurations for the Confluent Platform interceptor. + The config override feature can be used to pass the configuration settings. + items: + type: string + type: array + consumer: + description: |- + consumer specifies the consumer configuration for the interceptor. If not + configured, it uses the Kafka dependency configuration. + properties: + authentication: + description: authentication defines the authentication + for the Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another + way to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap + endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + discovery: + description: discovery specifies the capability to discover + the Kafka cluster. + properties: + name: + description: name is the name of the Confluent Platform + component cluster. + type: string + namespace: + description: |- + namespace is where the Confluent Platform component is running. + The default value is the namespace where CFK is running. + type: string + secretRef: + description: secretRef is the name of the secret used + to discover the Confluent Platform component. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + tls: + description: tls defines the client-side TLS setting for + the Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + enabled: + description: enabled indicates whether the Confluent Platform + interceptor is enabled or disabled. + type: boolean + producer: + description: |- + producer specifies the producer configuration for the interceptor. If not + configured, it uses the Kafka dependency configuration. + properties: + authentication: + description: authentication defines the authentication + for the Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another + way to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap + endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + discovery: + description: discovery specifies the capability to discover + the Kafka cluster. + properties: + name: + description: name is the name of the Confluent Platform + component cluster. + type: string + namespace: + description: |- + namespace is where the Confluent Platform component is running. + The default value is the namespace where CFK is running. + type: string + secretRef: + description: secretRef is the name of the secret used + to discover the Confluent Platform component. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + tls: + description: tls defines the client-side TLS setting for + the Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + publishMs: + type: integer + required: + - enabled + type: object + kafka: + description: kafka contains the Connect dependency for connecting + to Kafka. The discovery method is used if this is not specified. + properties: + authentication: + description: authentication defines the authentication for + the Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another way + to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap + endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + discovery: + description: discovery specifies the capability to discover + the Kafka cluster. + properties: + name: + description: name is the name of the Confluent Platform + component cluster. + type: string + namespace: + description: |- + namespace is where the Confluent Platform component is running. + The default value is the namespace where CFK is running. + type: string + secretRef: + description: secretRef is the name of the secret used + to discover the Confluent Platform component. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + tls: + description: tls defines the client-side TLS setting for the + Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + licenseCluster: + description: |- + licenseCluster contains the security configuration to connect to the License containing Kafka cluster.Note that this entry is only needed + if the license topic is stored on a different Kafka cluster than the Kafka cluster that Connect uses. + properties: + kafka: + description: |- + KafkaClientDependency configures the Confluent Platform component dependency + for the Kafka cluster. + properties: + authentication: + description: authentication defines the authentication + for the Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another + way to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap + endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + discovery: + description: discovery specifies the capability to discover + the Kafka cluster. + properties: + name: + description: name is the name of the Confluent Platform + component cluster. + type: string + namespace: + description: |- + namespace is where the Confluent Platform component is running. + The default value is the namespace where CFK is running. + type: string + secretRef: + description: secretRef is the name of the secret used + to discover the Confluent Platform component. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + tls: + description: tls defines the client-side TLS setting for + the Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + topic: + description: The name of the Kafka topic where the license + is stored. This defaults to _confluent-command. + type: string + type: object + mds: + description: mds contains the configuration for MDS dependency + when RBAC is enabled. + properties: + authentication: + description: authentication specifies the client side authentication + configuration for the MDS. + properties: + bearer: + description: bearer specifies the bearer authentication + settings. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: oauth specifies the OAuth authentication + settings. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass + the basic credential through a directory path in + the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to + pass the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication method + for the MDS. The valid option is `bearer`, `oauth`. + enum: + - bearer + - oauth + type: string + required: + - type + type: object + endpoint: + description: endpoint specifies the MDS endpoint. + minLength: 1 + pattern: ^https?://.* + type: string + ssoProtocol: + description: sso protocol, valid options are ldap and oidc. + enum: + - ldap + - oidc + type: string + tls: + description: ClientTLSConfig specifies the TLS configuration + for the Confluent component (dependencies, listeners). + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + tokenKeyPair: + description: tokenKeyPair specifies the token keypair to configure + the MDS. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer defines the directory path in the container + where the MDS token key pair are mounted. + minLength: 1 + type: string + encryptedTokenKey: + description: |- + EncryptedTokenKey boolean value indicating whether the tokenKeypair(private used for signing) is encrypted using a passphrase. If true, cfk + operator will look for a file named mdsTokenKeyPassphrase.txt containing key value pair + mdsTokenKeyPassphrase=. Relevant only for mds server. Ignored if set for a client configuration. + type: boolean + secretRef: + description: secretRef references the name of the secret + that contains the MDS token key pair. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + required: + - authentication + - endpoint + - tokenKeyPair + type: object + producer: + description: |- + producer contains the security configuration to connect to the Kafka cluster. It is used for source connectors. + If `bootstrapEndpoint` is not configured, the security is configured based on the Kafka dependency configuration. + Configure this property if different bootstrap endpoint of security is required for the producer. + properties: + authentication: + description: authentication defines the authentication for + the Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another way + to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap + endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + discovery: + description: discovery specifies the capability to discover + the Kafka cluster. + properties: + name: + description: name is the name of the Confluent Platform + component cluster. + type: string + namespace: + description: |- + namespace is where the Confluent Platform component is running. + The default value is the namespace where CFK is running. + type: string + secretRef: + description: secretRef is the name of the secret used + to discover the Confluent Platform component. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + tls: + description: tls defines the client-side TLS setting for the + Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + schemaRegistry: + description: schemaRegistry contains the dependency configuration + for the Schema Registry cluster. + properties: + authentication: + description: authentication specifies the authentication for + the Schema Registry cluster. + properties: + basic: + description: basic specifies the configuration for basic + authentication. + properties: + debug: + description: debug enables the basic authentication + debug logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: OAuth specifies the configuration for OAuth + authentication. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass + the basic credential through a directory path in + the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to + pass the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication scheme + for the REST API client. Valid options are `basic` and + `mtls`. + enum: + - basic + - mtls + - oauth + type: string + required: + - type + type: object + tls: + description: tls defines the client-side TLS setting for the + Schema Registry cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + url: + description: url specifies the URL endpoint of the Schema + Registry cluster. + minLength: 1 + pattern: ^https?://.* + type: string + required: + - url + type: object + type: object + enableExternalInterInstance: + description: |- + ExternalInterInstance is only needed for multi-cluster deployment or stretch cluster. + when set to true the connect server will use the external listener for inter-instance communication. + type: boolean + enableSchemas: + description: enableSchemas indicates whether to enable scheme or not. + type: boolean + externalAccess: + description: CPExternalAccess holds all external access policies for + the non-Kafka component clusters. + properties: + loadBalancer: + description: loadBalancer specifies the configuration to create + a Kubernetes load balancer service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and value + pairs. It specifies Kubernetes annotations for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain is the domain name of the component cluster. + minLength: 1 + type: string + externalTrafficPolicy: + description: externalTrafficPolicy specifies the external + traffic policy for the service. Valid options are `Local` + and `Cluster`. + enum: + - Local + - Cluster + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value pairs. + It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + loadBalancerSourceRanges: + description: loadBalancerSourceRanges specify the source ranges. + items: + type: string + type: array + port: + description: |- + port specifies the external port for the client consumption. + If not configured, the same internal/external port is configured for the component. + Information about the port can be retrieved through the status API. + format: int32 + type: integer + prefix: + description: |- + prefix specify the prefix for the given domain. + The default value is the name of the cluster. + minLength: 1 + type: string + servicePorts: + description: servicePorts specify the user-provided service + port(s). + items: + description: ServicePort contains information on service's + port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations of Client + IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - domain + type: object + nodePort: + description: nodePort specifies the configuration to create a + Kubernetes node port service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to `://:, where`podId` starts from `0` to `replicaCount - 1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and value + pairs. It specifies Kubernetes annotations for this service. + type: object + x-kubernetes-map-type: granular + externalTrafficPolicy: + description: |- + externalTrafficPolicy specifies the external traffic policy for the service. + Valid options are `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + host: + description: host defines the host name of the cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value pairs. + It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + nodePortOffset: + description: |- + nodePortOffset specifies the starting offset of the node ports. The port numbers go in ascending order with respect + to the replicas count. + NodePort service creation fails if the node port is not in the range supported by the Kubernetes API server. + The default Kubernetes Node Port range is `30000` - `32762`. + format: int32 + minimum: 0 + type: integer + servicePorts: + description: |- + servicePorts specify user-provided service port(s). + For Kafka with the nodePort type, this setting is only applied to Kafka bootstrap service. + items: + description: ServicePort contains information on service's + port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations of Client + IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - host + - nodePortOffset + type: object + route: + description: route specifies the configuration to create a route + service in OpenShift. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://-http-external.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and value + pairs. It specifies Kubernetes annotations for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain specifies the domain name of the Confluent + component cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value pairs. + It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + prefix: + description: |- + prefix specifies the component prefix when configured for the domain. + The default value is the name of the cluster. + minLength: 1 + type: string + wildcardPolicy: + description: |- + wildcardPolicy allows you to define a route that covers all hosts within a domain. Valid options are `Subdomain` and `None`. + The default value is `None`. + enum: + - Subdomain + - None + type: string + required: + - domain + type: object + type: + description: |- + type specifies the Kubernetes external service for the component. + Valid options are `loadBalancer`, `nodePort`, and `route`. + enum: + - loadBalancer + - nodePort + - route + minLength: 1 + type: string + required: + - type + type: object + headlessService: + description: headlessService specifies the configuration of the Kubernetes + headless service. + properties: + annotations: + additionalProperties: + type: string + description: |- + annotations is a map of string key and value pairs. + It specifies the annotations to be added to the CFK-created headless service. + These annotations are merged with the injectAnnotations and take precedence. + type: object + x-kubernetes-map-type: granular + labels: + additionalProperties: + type: string + description: |- + labels is a map of string key and value pairs. + It specifies the labels to be added to the CFK-created headless service. + These labels are merged with the injectLabels and take precedence. + type: object + x-kubernetes-map-type: granular + publishNotReadyAddresses: + description: |- + publishNotReadyAddresses specifies the publishNotReadyAddresses field. + For Kafka, this value must be true. The default value is true. + type: boolean + type: object + image: + description: |- + image specifies the application and the init docker image configurations. + A change to this setting will roll the cluster. + properties: + application: + description: |- + application is the Docker image name of the application. Specify + `//:`. + pattern: .+:.+ + type: string + init: + description: |- + init is the init-container name. Specify + `//:`. + pattern: .+:.+ + type: string + pullPolicy: + description: |- + pullPolicy is the policy for pulling images. Valid options are `Always`, `Never`, and `IfNotPresent`. + The default value is `IfNotPresent`. + enum: + - Always + - Never + - IfNotPresent + type: string + pullSecretRef: + description: |- + pullSecretRef references the secrets in the same namespace to be used for pulling images. + Image pull secrets are distinct from secrets because secrets + can be mounted in the pod, but image pull secrets are only accessed by `kubelet`. + More info: https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod + items: + type: string + type: array + required: + - application + - init + type: object + injectAnnotations: + additionalProperties: + type: string + description: |- + injectAnnotations are the annotations injected to the internal resources that CFK created. + The internal annotations are preserved and cannot be overridden. + For pod annotations, use `podTemplate.annotations`. + type: object + x-kubernetes-map-type: granular + injectLabels: + additionalProperties: + type: string + description: |- + injectLabels are the labels injected to the internal resources that CFK created. + The internal labels are preserved and cannot be overridden. + For pod labels, use `podTemplate.labels`. + type: object + x-kubernetes-map-type: granular + internalTopicReplicationFactor: + description: |- + internalTopicReplicationFactor specifies the replication factor for the internal topics. + The default value is `3`. + format: int32 + type: integer + k8sClusterDomain: + description: |- + k8sClusterDomain specifies the configuration of the Kubernetes cluster domain. + The default is the `cluster.local` domain. + type: string + keyConverterType: + description: |- + keyConverterType specifies the supported key converters package for the Confluent Platform. + For the supported converter types, see https://docs.confluent.io/current/connect/concepts.html#connect-converters. + The default value is `org.apache.kafka.connect.json.JsonConverter`. + minLength: 1 + type: string + license: + description: license specifies the license configuration for the Confluent + Platform component. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + the license key is mounted. More info: + https://docs.confluent.io/operator/current/co-license.html#update-component-level-licenses + minLength: 1 + type: string + globalLicense: + description: globalLicense specifies whether the Confluent Platform + component shares the common global license. + type: boolean + secretRef: + description: |- + secretRef references the secret that provides the license for the Confluent Platform component. + More info: https://docs.confluent.io/operator/current/co-license.html#update-component-level-licenses + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + metrics: + description: metrics specify the security settings for the metric + services. + properties: + authentication: + description: authentication specifies the authentication configuration + for the metrics. + properties: + type: + description: type specifies the metrics authentication method. + The valid option is `mtls`. + enum: + - mtls + type: string + required: + - type + type: object + prometheus: + description: prometheus specifies the configuration overrides + for the JMX-Prometheus exporter. + properties: + blacklist: + items: + type: string + type: array + rules: + items: + description: Rule defines the Prometheus Exporter rule override. + properties: + attrNameSnakeCase: + type: boolean + cache: + type: boolean + help: + minLength: 1 + type: string + labels: + additionalProperties: + type: string + type: object + x-kubernetes-map-type: granular + name: + minLength: 1 + type: string + pattern: + minLength: 1 + type: string + type: + minLength: 1 + type: string + value: + minLength: 1 + type: string + valueFactor: + anyOf: + - type: integer + - type: string + default: 1 + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: array + whitelist: + items: + type: string + type: array + type: object + tls: + description: tls specifies the TLS configuration for the metrics. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + mountedSecrets: + description: |- + mountedSecrets list the secrets injected to + the underlying statefulset configuration. The secret reference is mounted + in the default path `/mnt/secrets/`. The underlying resources + will follow the secret as a file configuration. + More info: https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets-as-files-from-a-pod. + A change to this setting will roll the cluster. + items: + description: |- + MountedSecrets provides a way to inject a custom secret to the underlying + statefulset. + properties: + keyItems: + description: keyItems are key and path names. + items: + description: Maps a string key to a path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + secretRef: + description: secretRef references the name of the secret. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + type: array + mountedVolumes: + description: |- + mountedVolumes list the custom volumes that need to be mounted into the + underlying statefulset. + A change to this setting will roll the cluster. + properties: + volumeMounts: + description: |- + volumeMounts specify the list of volume mounts for the pods in the + statefulset. + items: + description: VolumeMount describes a mounting of a Volume within + a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + description: |- + volumes specify the list of volumes that can be mounted into the pods + of statefulset. + items: + description: Volume represents a named volume in a pod that + may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + format: int32 + type: integer + readOnly: + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: boolean + volumeID: + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + required: + - volumeID + type: object + azureDisk: + description: azureDisk represents an Azure Data Disk mount + on the host and bind mount to the pod. + properties: + cachingMode: + description: 'cachingMode is the Host Caching mode: + None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the data disk in + the blob storage + type: string + diskURI: + description: diskURI is the URI of data disk in the + blob storage + type: string + fsType: + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: multiple + blob disks per storage account Dedicated: single + blob disk per storage account Managed: azure managed + data disk (only in managed availability set). defaults + to shared' + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: azureFile represents an Azure File Service + mount on the host and bind mount to the pod. + properties: + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of secret that + contains Azure Storage Account Name and Key + type: string + shareName: + description: shareName is the azure share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: cephFS represents a Ceph FS mount on the host + that shares a pod's lifetime + properties: + monitors: + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + items: + type: string + type: array + path: + description: 'path is Optional: Used as the mounted + root, rather than the full Ceph tree, default is /' + type: string + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: boolean + secretFile: + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + secretRef: + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + required: + - monitors + type: object + cinder: + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: boolean + secretRef: + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + description: csi (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers (Beta feature). + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward API about the + pod that should populate this volume + properties: + defaultMode: + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents information + to create the file containing the pod field + properties: + fieldRef: + description: 'Required: Selects a field of the + pod: only annotations, labels, name and namespace + are supported.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must not + be absolute or contain the ''..'' path. Must + be utf-8 encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + emptyDir: + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + properties: + medium: + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: 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]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. + properties: + volumeClaimTemplate: + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + + Required, must not be nil. + properties: + metadata: + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. + type: object + spec: + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim + are also valid here. + properties: + accessModes: + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + items: + type: string + type: array + dataSource: + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + 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. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any 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. + 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 + required: + - kind + - name + type: object + resources: + description: |- + resources represents the minimum resources the volume should have. + If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + selector: + description: selector is a label query over + volumes to consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + type: string + volumeAttributesClassName: + description: |- + volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. + If specified, the CSI driver will create or update the volume with the attributes defined + in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, + it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass + will be applied to the claim but it's not allowed to reset this field to empty string once it is set. + If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass + will be set by the persistentvolume controller if it exists. + If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be + set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource + exists. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#volumeattributesclass + (Alpha) Using this field requires the VolumeAttributesClass feature gate to be enabled. + type: string + volumeMode: + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding reference + to the PersistentVolume backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource that + is attached to a kubelet's host machine and then exposed + to the pod. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + lun: + description: 'lun is Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC target worldwide + names (WWNs)' + items: + type: string + type: array + wwids: + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. + items: + type: string + type: array + type: object + flexVolume: + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. + properties: + driver: + description: driver is the name of the driver to use + for this volume. + type: string + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this field holds + extra command options if any.' + type: object + readOnly: + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + description: flocker represents a Flocker volume attached + to a kubelet's host machine. This depends on the Flocker + control service being running + properties: + datasetName: + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of the dataset. + This is unique identifier of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + properties: + fsType: + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + format: int32 + type: integer + pdName: + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: boolean + required: + - pdName + type: object + gitRepo: + description: |- + gitRepo represents a git repository at a particular revision. + DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. + properties: + directory: + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash for the specified + revision. + type: string + required: + - repository + type: object + glusterfs: + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/glusterfs/README.md + properties: + endpoints: + description: |- + endpoints is the endpoint name that details Glusterfs topology. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + path: + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + readOnly: + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- + TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not + mount host directories as read/write. + properties: + path: + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + type: + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + required: + - path + type: object + iscsi: + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://examples.k8s.io/volumes/iscsi/README.md + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether support + iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether support + iSCSI Session CHAP authentication + type: boolean + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + initiatorName: + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified Name. + type: string + iscsiInterface: + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret for iSCSI + target and initiator authentication + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + nfs: + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + properties: + path: + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + readOnly: + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: boolean + server: + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + properties: + claimName: + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + type: string + readOnly: + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: photonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets host + machine + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies Photon Controller + persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: portworxVolume represents a portworx volume + attached and mounted on kubelets host machine + properties: + fsType: + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one resources secrets, + configmaps, and downward API + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + sources: + description: sources is the list of volume projections + items: + description: Projection that may be projected along + with other supported volume types + properties: + clusterTrustBundle: + description: |- + ClusterTrustBundle allows a pod to access the `.spec.trustBundle` field + of ClusterTrustBundle objects in an auto-updating file. + + + Alpha, gated by the ClusterTrustBundleProjection feature gate. + + + ClusterTrustBundle objects can either be selected by name, or by the + combination of signer name and a label selector. + + + Kubelet performs aggressive normalization of the PEM contents written + into the pod filesystem. Esoteric PEM features such as inter-block + comments and block headers are stripped. Certificates are deduplicated. + The ordering of certificates within the file is arbitrary, and Kubelet + may change the order over time. + properties: + labelSelector: + description: |- + Select all ClusterTrustBundles that match this label selector. Only has + effect if signerName is set. Mutually-exclusive with name. If unset, + interpreted as "match nothing". If set but empty, interpreted as "match + everything". + 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 + name: + description: |- + Select a single ClusterTrustBundle by object name. Mutually-exclusive + with signerName and labelSelector. + type: string + optional: + description: |- + If true, don't block pod startup if the referenced ClusterTrustBundle(s) + aren't available. If using name, then the named ClusterTrustBundle is + allowed not to exist. If using signerName, then the combination of + signerName and labelSelector is allowed to match zero + ClusterTrustBundles. + type: boolean + path: + description: Relative path from the volume + root to write the bundle. + type: string + signerName: + description: |- + Select all ClusterTrustBundles that match this signer name. + Mutually-exclusive with name. The contents of all selected + ClusterTrustBundles will be unified and deduplicated. + type: string + required: + - path + type: object + configMap: + description: configMap information about the configMap + data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional specify whether the + ConfigMap or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about the + downwardAPI data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a field + of the pod: only annotations, labels, + name and namespace are supported.' + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in terms + of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to + select in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file to + be created. Must not be absolute or + contain the ''..'' path. Must be utf-8 + encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env + vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + secret: + description: secret information about the secret + data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional field specify whether + the Secret or its key must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information + about the serviceAccountToken data to project + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + format: int64 + type: integer + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + description: quobyte represents a Quobyte mount on the host + that shares a pod's lifetime + properties: + group: + description: |- + group to map volume access to + Default is no group + type: string + readOnly: + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes + type: string + tenant: + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: |- + user to map volume access to + Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references an already + created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/rbd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + image: + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + keyring: + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + monitors: + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + items: + type: string + type: array + pool: + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: boolean + secretRef: + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + required: + - image + - monitors + type: object + scaleIO: + description: scaleIO represents a ScaleIO persistent volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". + type: string + gateway: + description: gateway is the host address of the ScaleIO + API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name of the ScaleIO + Protection Domain for the configured storage. + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + description: sslEnabled Flag enable/disable SSL communication + with Gateway, default false + type: boolean + storageMode: + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO Storage Pool + associated with the protection domain. + type: string + system: + description: system is the name of the storage system + as configured in ScaleIO. + type: string + volumeName: + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: optional field specify whether the Secret + or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + type: object + storageos: + description: storageOS represents a StorageOS volume attached + and mounted on Kubernetes nodes. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. + type: string + volumeNamespace: + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: vsphereVolume represents a vSphere volume attached + and mounted on kubelets host machine + properties: + fsType: + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage Policy Based + Management (SPBM) profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage Policy + Based Management (SPBM) profile name. + type: string + volumePath: + description: volumePath is the path that identifies + vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + required: + - volumeMounts + - volumes + type: object + oneReplicaPerNode: + description: |- + oneReplicaPerNode controls whether to run 1 pod per node using the pod anti-affinity capability. + Enabling this configuration in an existing cluster will roll the cluster. + type: boolean + pdb: + description: |- + configures PodDisruptionBudget for the Confluent Platform component. + by default PDB is configured based on pre-detemined formula. + properties: + enabled: + description: enabled specifies whether the PodDisruptionBudget + is enabled + type: boolean + maxUnavailable: + description: maxUnavailable is the maximum number of pods that + can be unavailable during the disruption. + format: int32 + type: integer + required: + - enabled + type: object + podTemplate: + description: podTemplate specifies the statefulset pod template configuration. + properties: + affinity: + description: |- + affinity specifies a group of affinity scheduling rules. + More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + type: array + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + annotations: + additionalProperties: + type: string + description: |- + annotations is a map of string key and value pairs stored with the resource and + may be set by external tools to store and retrieve arbitrary metadata. They + are not queryable and should be preserved when modifying objects. More + info: http://kubernetes.io/docs/user-guide/annotations. + type: object + x-kubernetes-map-type: granular + envVars: + description: |- + envVars contain environment variables to be injected into containers. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container. + 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 + labels: + additionalProperties: + type: string + description: |- + labels is a map of string key and value pairs that can be used to organize and categorize + (scope and select) objects. + More info: http://kubernetes.io/docs/user-guide/labels. + type: object + x-kubernetes-map-type: granular + podSecurityContext: + description: |- + PodSecurityContext holds pod-level security attributes and common container settings. + Some fields are also present in container.securityContext. Field values of + container.securityContext take precedence over field values of PodSecurityContext. + 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: + + + 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---- + + + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + 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 + type: object + 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. + 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: + + + 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 + required: + - type + type: object + 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. + items: + format: int64 + type: integer + type: array + 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. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + 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. + 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 + type: object + type: object + priorityClassName: + description: priorityClassName specifies the priority class for + the pod (if any). + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + probe: + description: probe contains the fields for standard Kubernetes + readiness/liveness probe configuration. + properties: + liveness: + description: |- + liveness configures the Kubernetes probe settings. The changes + will override the existing default configuration. + properties: + failureThreshold: + description: |- + failureThreshold is the minimum consecutive failures for the probe to be considered failed. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + initialDelaySeconds: + description: |- + initialDelaySeconds is the number of seconds after the container has started and before probes are initiated. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + path: + description: Path for the HTTP probe + type: string + periodSeconds: + description: |- + periodSeconds specifies how often to perform the probe. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + port: + description: Number of the port to access on the container + type: integer + successThreshold: + description: |- + successThreshold is the minimum consecutive successes for the probe to be considered successful after having failed. + The default values is `1`. Must be `1` for liveness and startup. The minimum value is `1`. + format: int32 + type: integer + timeoutSeconds: + description: |- + timeoutSeconds is the number of seconds after which the probe times out. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + type: object + readiness: + description: |- + readiness configures the Kubernetes probe setting. The changes + will override the existing default configuration. + properties: + failureThreshold: + description: |- + failureThreshold is the minimum consecutive failures for the probe to be considered failed. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + initialDelaySeconds: + description: |- + initialDelaySeconds is the number of seconds after the container has started and before probes are initiated. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + path: + description: Path for the HTTP probe + type: string + periodSeconds: + description: |- + periodSeconds specifies how often to perform the probe. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + port: + description: Number of the port to access on the container + type: integer + successThreshold: + description: |- + successThreshold is the minimum consecutive successes for the probe to be considered successful after having failed. + The default values is `1`. Must be `1` for liveness and startup. The minimum value is `1`. + format: int32 + type: integer + timeoutSeconds: + description: |- + timeoutSeconds is the number of seconds after which the probe times out. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + type: object + type: object + resources: + description: resources describe 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. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: |- + SecurityContext holds security configuration that will be applied to a container. + Some fields are present in both SecurityContext and PodSecurityContext. When both + are set, the values in SecurityContext take precedence. + 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. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + 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 + type: object + 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. + 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: + + + 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 + required: + - type + type: object + 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. + 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 + type: object + type: object + serviceAccountName: + description: |- + ServiceAccountName is the name of the service account used to run this pod. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account. + type: string + terminationGracePeriodSeconds: + description: terminationGracePeriodSeconds is the grace period + before the pod is deleted. + format: int64 + type: integer + tolerations: + description: |- + tolerations specify the pods to schedule onto the nodes with matching taints, using + the triple `` and the matching operator ``. + 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 + topologySpreadConstraints: + description: |- + topologySpreadConstraints describe how a group of pods ought to spread across topology domains. Scheduler will + schedule pods based on the constraints. All topologySpreadConstraints are ANDed. + items: + description: TopologySpreadConstraint specifies how to spread + matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + 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. + + + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + 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. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + + + This is a beta field and requires the MinDomainsInPodTopologySpread feature gate to be enabled (enabled by default). + format: int32 + type: integer + 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. + + + 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. + + + 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 + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + replicas: + description: |- + replicas is the desired number of replicas. + A change to this setting will roll the cluster. + format: int32 + type: integer + telemetry: + description: telemetry specifies the Confluent telemetry reporter + configuration. + properties: + global: + description: |- + global allows disabling telemetry configuration. + If CFK is deployed with telemetry, this field is only + used to disable telemetry. The default value is `true` if + telemetry is enabled at the global level. + type: boolean + type: object + tls: + description: tls specifies the global-level TLS configuration. + properties: + autoGeneratedCerts: + description: |- + autoGeneratedCerts specifies that the certificates are auto-generated based on + the CA key pair provided. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + fips: + description: |- + fips specifies the configuration of FIPS compliant Bouncy Castle type Java Keystores for the cp component's + TLS settings. TLS Secrets must have the keys keystore.bcfks, truststore.bcfks, and jksPassword.txt + properties: + enabled: + description: enabled specifies whether to enable the FIPS + configuration for cp components. + type: boolean + required: + - enabled + type: object + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing the + JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + valueConverterType: + description: |- + valueConverterType specifies the supported value converters package for the Confluent Platform. + For the supported converter types, see https://docs.confluent.io/current/connect/concepts.html#connect-converters. + The default value is `org.apache.kafka.connect.json.JsonConverter`. + minLength: 1 + type: string + required: + - image + type: object + status: + description: status defines the observed state of the Connect cluster. + properties: + arbitraryData: + description: arbitraryData is the map for any arbitrary data associated + with this Confluent component. + x-kubernetes-preserve-unknown-fields: true + authorizationType: + description: authorizationType is the authorization type for this + Confluent component. + type: string + clusterName: + description: clusterName is the name of the Confluent Platform component + cluster. + type: string + clusterNamespace: + description: clusterNamespace is the namespace where the Confluent + Platform component cluster is running. + type: string + conditions: + description: conditions specify the latest available observations + of the current state. + items: + description: Condition represent the latest available observations + of the current state. + properties: + lastProbeTime: + description: lastProbeTime shows the last time the condition + was evaluated. + format: date-time + type: string + lastTransitionTime: + description: lastTransitionTime shows the last time the condition + was transitioned from one status to another. + format: date-time + type: string + message: + description: message shows a human-readable message with details + about the transition. + type: string + reason: + description: reason shows the reason for the last transition + of the condition. + type: string + status: + description: status shows the status of the condition, one of + `True`, `False`, or `Unknown`. + type: string + type: + description: type shows the condition type. + type: string + type: object + type: array + connectorPlugins: + description: connectorPlugins are the installed connector plugins. + items: + description: ConnectorPluginStatus defines the state of the connector + plugin. + properties: + class: + description: class specifies the class name of the connector + plugin. + type: string + type: + description: type is the connector plugin type, which can be + `SOURCE`, `SINK` or `UNKNOWN`. + type: string + version: + description: version is the current version of the connector + plugin. + type: string + required: + - class + type: object + type: array + connectorTLSFilePaths: + description: connectorTLSFilePaths are the connector TLS file paths. + items: + description: CustomTLSFilePathStatus specifies the file paths of + the custom TLS certificates. + properties: + jksPasswordPath: + description: jksPasswordPath contains the absolute path of the + `jksPassword.txt` file. + type: string + keyStorePath: + description: keyStorePath contains the absolute path of the + keystore file, `.jks` or `.p12`. + type: string + trustStorePath: + description: trustStorePath contains the absolute path of the + truststore file, `.jks` or `.p12`. + type: string + type: object + type: array + currentReplicas: + description: currentReplicas is the number of currently running replicas. + format: int32 + type: integer + groupID: + description: groupID is the group id of the Connect cluster. + type: string + internalSecrets: + description: |- + internalSecrets are internal secrets created + by CFK for this Confluent component. + items: + type: string + type: array + internalTopicNames: + description: internalTopicNames are the topics used by the component + for internal use. + items: + type: string + type: array + kafka: + description: kafka is the Kafka client side status for the Connect + cluster. + properties: + authenticationType: + description: authenticationType describes the authentication method + for the Kafka cluster. + type: string + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap endpoint. + type: string + tls: + description: tls indicates whether TLS is enabled for the Kafka + dependency. + type: boolean + type: object + observedGeneration: + description: observedGeneration is the most recent generation observed + for this Confluent component. + format: int64 + type: integer + operatorVersion: + description: operatorVersion is the internal version of CFK. + type: string + phase: + description: |- + phase describes the state of the Confluent Platform component. This can either be 'PROVISIONING' + or 'RUNNING' + 'PROVISIONING' means the Confluent Platform component is currently getting deployed and not ready yet. + 'RUNNING' means the Confluent Platform component has been successfully deployed. + type: string + rbac: + description: rbac contains the RBAC-related status when RBAC is enabled. + properties: + clusterID: + description: clusterID specifies the id of the cluster. + type: string + internalRolebindings: + description: internalRolebindings specifies the internal rolebindings. + items: + type: string + type: array + type: object + readyReplicas: + description: readyReplicas is the number of currently ready replicas. + format: int32 + type: integer + replicas: + description: replicas is the number of replicas. + format: int32 + type: integer + restConfig: + description: restConfig is the REST configuration of the Connect cluster. + properties: + advertisedExternalEndpoints: + description: advertisedExternalEndpoints specifies other advertised + endpoints used, especially for Kafka. + items: + type: string + type: array + authenticationType: + description: authenticationType shows the authentication type + configured by the listener. + type: string + externalAccessType: + description: externalAccessType shows the external access type + used for the listener. + type: string + externalEndpoint: + description: externalEndpoint specifies the external endpoint + to connect to the Confluent component cluster. + type: string + internalEndpoint: + description: internalEndpoint specifies the internal endpoint + to connect to the Confluent component cluster. + type: string + tls: + description: tls shows whether TLS is configured for the listener. + type: boolean + type: object + selector: + description: |- + selector gets the label selector of the child pod. + The Horizontal Pod Autoscaler(HPA) will scale using the label selector of the child pod. + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + scale: + labelSelectorPath: .status.selector + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + status: {} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_controlcenters.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_controlcenters.yaml new file mode 100644 index 000000000..9571eddff --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_controlcenters.yaml @@ -0,0 +1,6393 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: controlcenters.platform.confluent.io +spec: + group: platform.confluent.io + names: + categories: + - all + - confluent-platform + - confluent + kind: ControlCenter + listKind: ControlCenterList + plural: controlcenters + shortNames: + - controlcenter + - c3 + singular: controlcenter + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.replicas + name: Replicas + type: string + - jsonPath: .status.readyReplicas + name: Ready + type: string + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.kafka.bootstrapEndpoint + name: Kafka + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: ControlCenter is the schema for the Control Center 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: spec defines the desired state of the Control Center cluster. + properties: + authentication: + description: authentication specifies the authentication configurations. + properties: + basic: + description: basic specifies the configuration for basic authentication. + properties: + debug: + description: debug enables the basic authentication debug + logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + ldap: + description: ldap specifies the configuration for Control Center + LDAP authentication. + properties: + debug: + description: debug enables basic authentication debug logs + for JaaS configuration. + type: boolean + property: + additionalProperties: + type: string + description: |- + property is a map of string key and value pairs that specifies the LDAP configuration. + Use a secret object to pass username/password. + type: object + x-kubernetes-map-type: granular + restrictedRoles: + description: restrictedRoles specify the restricted access + roles. + items: + type: string + minItems: 1 + type: array + roles: + description: roles specify the roles on the server side only. + items: + type: string + minItems: 1 + type: array + secretRef: + description: |- + secretRef references the secret to pass required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#ldap-authentication-for-c3 + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: type specifies the authentication type of the Control + Center. Valid options are `basic`, `ldap`, and `mtls`. + enum: + - basic + - ldap + - mtls + type: string + required: + - type + type: object + authorization: + description: authorization specifies the authorization configurations. + properties: + kafkaRestClassRef: + description: |- + kafkaRestClassRef references the KafkaRestClass + which specifies the Kafka REST API connection configuration. + properties: + name: + description: name specifies the name of the KafkaRestClass + application resource. + minLength: 1 + type: string + namespace: + description: namespace specifies the namespace of the KafkaRestClass. + type: string + required: + - name + type: object + type: + description: type specifies the client-side authorization type. + The valid option is `rbac`. + enum: + - rbac + type: string + required: + - type + type: object + configOverrides: + description: configOverrides specifies the configs to override the + server, JVM, Log4j properties for the Control Center. + properties: + jvm: + description: |- + jvm is a list of JVM configuration supported by the Confluent Platform component. + This will either add or update the existing configuration. + items: + type: string + type: array + log4j: + description: |- + log4j is a list of Log4J configuration supported by the Confluent Platform component. + This will either add or update the existing configuration. + items: + type: string + type: array + server: + description: |- + server is a list of server configuration supported by the Confluent Platform component. + This will either add or update existing configuration. + items: + type: string + type: array + type: object + dataVolumeCapacity: + anyOf: + - type: integer + - type: string + description: dataVolumeCapacity specifies the data size for the persistent + volume. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + dependencies: + description: dependencies specify the dependencies configurations. + properties: + connect: + description: connect defines the Connect worker dependency configurations. + items: + description: ControlCenterConnectDependency defines the Connect + dependency settings. + properties: + authentication: + description: authentication specifies the authentication + configuration for the Connect cluster. + properties: + basic: + description: basic specifies the configuration for basic + authentication. + properties: + debug: + description: debug enables the basic authentication + debug logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: OAuth specifies the configuration for OAuth + authentication. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience + claim in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max + retry backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry + backoff with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of + claim in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to + pass the basic credential through a directory + path in the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference + to pass the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication scheme + for the REST API client. Valid options are `basic` + and `mtls`. + enum: + - basic + - mtls + - oauth + type: string + required: + - type + type: object + name: + description: name specifies the Connect cluster name. + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + tls: + description: tls specifies the client-side TLS setting for + the Connect cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + url: + description: url specifies the URL endpoint of the Connect + cluster. + minLength: 1 + pattern: ^https?://.* + type: string + required: + - name + - url + type: object + type: array + kafka: + description: kafka defines the Kafka dependency configurations. + properties: + authentication: + description: authentication defines the authentication for + the Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another way + to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap + endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + discovery: + description: discovery specifies the capability to discover + the Kafka cluster. + properties: + name: + description: name is the name of the Confluent Platform + component cluster. + type: string + namespace: + description: |- + namespace is where the Confluent Platform component is running. + The default value is the namespace where CFK is running. + type: string + secretRef: + description: secretRef is the name of the secret used + to discover the Confluent Platform component. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + tls: + description: tls defines the client-side TLS setting for the + Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + ksqldb: + description: ksqldb defines the ksqlDB dependency configurations. + items: + description: ControlCenterKSQLDependency defines the ksqlDB + dependency settings. + properties: + advertisedUrl: + description: advertisedUrl specifies the advertised URL + to use in the browser. + minLength: 1 + pattern: ^https?://.* + type: string + authentication: + description: authentication specifies the authentication + for the ksqlDB cluster. + properties: + basic: + description: basic specifies the configuration for basic + authentication. + properties: + debug: + description: debug enables the basic authentication + debug logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: OAuth specifies the configuration for OAuth + authentication. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience + claim in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max + retry backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry + backoff with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of + claim in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to + pass the basic credential through a directory + path in the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference + to pass the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication scheme + for the REST API client. Valid options are `basic` + and `mtls`. + enum: + - basic + - mtls + - oauth + type: string + required: + - type + type: object + name: + description: name specifies the ksqlDB cluster name. + minLength: 1 + type: string + tls: + description: tls specifies the client-side TLS setting for + the ksqlDB cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + url: + description: url specifies the URL endpoint of the ksqlDB + cluster. + minLength: 1 + pattern: ^https?://.* + type: string + required: + - name + - url + type: object + type: array + mds: + description: mds defines the RBAC dependency configurations. + properties: + authentication: + description: authentication specifies the client side authentication + configuration for the MDS. + properties: + bearer: + description: bearer specifies the bearer authentication + settings. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: oauth specifies the OAuth authentication + settings. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass + the basic credential through a directory path in + the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to + pass the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication method + for the MDS. The valid option is `bearer`, `oauth`. + enum: + - bearer + - oauth + type: string + required: + - type + type: object + endpoint: + description: endpoint specifies the MDS endpoint. + minLength: 1 + pattern: ^https?://.* + type: string + ssoProtocol: + description: sso protocol, valid options are ldap and oidc. + enum: + - ldap + - oidc + type: string + tls: + description: ClientTLSConfig specifies the TLS configuration + for the Confluent component (dependencies, listeners). + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + tokenKeyPair: + description: tokenKeyPair specifies the token keypair to configure + the MDS. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer defines the directory path in the container + where the MDS token key pair are mounted. + minLength: 1 + type: string + encryptedTokenKey: + description: |- + EncryptedTokenKey boolean value indicating whether the tokenKeypair(private used for signing) is encrypted using a passphrase. If true, cfk + operator will look for a file named mdsTokenKeyPassphrase.txt containing key value pair + mdsTokenKeyPassphrase=. Relevant only for mds server. Ignored if set for a client configuration. + type: boolean + secretRef: + description: secretRef references the name of the secret + that contains the MDS token key pair. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + required: + - authentication + - endpoint + - tokenKeyPair + type: object + schemaRegistry: + description: schemaRegistry defines the Schema Registry dependency + configurations. + properties: + authentication: + description: authentication specifies the authentication for + the Schema Registry cluster. + properties: + basic: + description: basic specifies the configuration for basic + authentication. + properties: + debug: + description: debug enables the basic authentication + debug logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: OAuth specifies the configuration for OAuth + authentication. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass + the basic credential through a directory path in + the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to + pass the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication scheme + for the REST API client. Valid options are `basic` and + `mtls`. + enum: + - basic + - mtls + - oauth + type: string + required: + - type + type: object + clusters: + items: + description: ControlCenterMultiSchemaRegistryDependency + defines the Schema Registry dependency List. + properties: + authentication: + description: authentication specifies the authentication + for the Schema Registry cluster. + properties: + basic: + description: basic specifies the configuration for + basic authentication. + properties: + debug: + description: debug enables the basic authentication + debug logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: OAuth specifies the configuration for + OAuth authentication. + properties: + configuration: + description: configuration specifies the OAuth + server settings. + properties: + audience: + description: audience specifies the audience + claim in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the + expected issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the + name of claim in token for identifying + the groups of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets + connect timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read + timeout with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets + max retry backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry + backoff with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name + of claim in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows + to pass the basic credential through a directory + path in the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference + to pass the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication scheme + for the REST API client. Valid options are `basic` + and `mtls`. + enum: + - basic + - mtls + - oauth + type: string + required: + - type + type: object + name: + description: name defines the Schema Registry cluster + name. + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + tls: + description: tls defines the client-side TLS setting + for the Schema Registry cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS + configuration for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + url: + description: url specifies the URL endpoint of the Schema + Registry cluster. + minLength: 1 + pattern: ^https?://.* + type: string + required: + - name + - url + type: object + type: array + tls: + description: tls defines the client-side TLS setting for the + Schema Registry cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + url: + description: url specifies the URL endpoint of the Schema + Registry cluster. + minLength: 1 + pattern: ^https?://.* + type: string + required: + - url + type: object + type: object + externalAccess: + description: externalAccess specifies the external access configuration + for the Control Center cluster. + properties: + loadBalancer: + description: loadBalancer specifies the configuration to create + a Kubernetes load balancer service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and value + pairs. It specifies Kubernetes annotations for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain is the domain name of the component cluster. + minLength: 1 + type: string + externalTrafficPolicy: + description: externalTrafficPolicy specifies the external + traffic policy for the service. Valid options are `Local` + and `Cluster`. + enum: + - Local + - Cluster + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value pairs. + It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + loadBalancerSourceRanges: + description: loadBalancerSourceRanges specify the source ranges. + items: + type: string + type: array + port: + description: |- + port specifies the external port for the client consumption. + If not configured, the same internal/external port is configured for the component. + Information about the port can be retrieved through the status API. + format: int32 + type: integer + prefix: + description: |- + prefix specify the prefix for the given domain. + The default value is the name of the cluster. + minLength: 1 + type: string + servicePorts: + description: servicePorts specify the user-provided service + port(s). + items: + description: ServicePort contains information on service's + port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations of Client + IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - domain + type: object + nodePort: + description: nodePort specifies the configuration to create a + Kubernetes node port service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to `://:, where`podId` starts from `0` to `replicaCount - 1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and value + pairs. It specifies Kubernetes annotations for this service. + type: object + x-kubernetes-map-type: granular + externalTrafficPolicy: + description: |- + externalTrafficPolicy specifies the external traffic policy for the service. + Valid options are `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + host: + description: host defines the host name of the cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value pairs. + It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + nodePortOffset: + description: |- + nodePortOffset specifies the starting offset of the node ports. The port numbers go in ascending order with respect + to the replicas count. + NodePort service creation fails if the node port is not in the range supported by the Kubernetes API server. + The default Kubernetes Node Port range is `30000` - `32762`. + format: int32 + minimum: 0 + type: integer + servicePorts: + description: |- + servicePorts specify user-provided service port(s). + For Kafka with the nodePort type, this setting is only applied to Kafka bootstrap service. + items: + description: ServicePort contains information on service's + port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations of Client + IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - host + - nodePortOffset + type: object + route: + description: route specifies the configuration to create a route + service in OpenShift. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://-http-external.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and value + pairs. It specifies Kubernetes annotations for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain specifies the domain name of the Confluent + component cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value pairs. + It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + prefix: + description: |- + prefix specifies the component prefix when configured for the domain. + The default value is the name of the cluster. + minLength: 1 + type: string + wildcardPolicy: + description: |- + wildcardPolicy allows you to define a route that covers all hosts within a domain. Valid options are `Subdomain` and `None`. + The default value is `None`. + enum: + - Subdomain + - None + type: string + required: + - domain + type: object + type: + description: |- + type specifies the Kubernetes external service for the component. + Valid options are `loadBalancer`, `nodePort`, and `route`. + enum: + - loadBalancer + - nodePort + - route + minLength: 1 + type: string + required: + - type + type: object + headlessService: + description: headlessService specifies the configuration of the Kubernetes + headless service. + properties: + annotations: + additionalProperties: + type: string + description: |- + annotations is a map of string key and value pairs. + It specifies the annotations to be added to the CFK-created headless service. + These annotations are merged with the injectAnnotations and take precedence. + type: object + x-kubernetes-map-type: granular + labels: + additionalProperties: + type: string + description: |- + labels is a map of string key and value pairs. + It specifies the labels to be added to the CFK-created headless service. + These labels are merged with the injectLabels and take precedence. + type: object + x-kubernetes-map-type: granular + publishNotReadyAddresses: + description: |- + publishNotReadyAddresses specifies the publishNotReadyAddresses field. + For Kafka, this value must be true. The default value is true. + type: boolean + type: object + id: + description: |- + id specifies the prefix used for this instance of Control Center + when multiple instances of Control Center co-exist. + format: int32 + type: integer + image: + description: |- + image specifies the application and the init docker image configurations. + A change to this setting will roll the cluster. + properties: + application: + description: |- + application is the Docker image name of the application. Specify + `//:`. + pattern: .+:.+ + type: string + init: + description: |- + init is the init-container name. Specify + `//:`. + pattern: .+:.+ + type: string + pullPolicy: + description: |- + pullPolicy is the policy for pulling images. Valid options are `Always`, `Never`, and `IfNotPresent`. + The default value is `IfNotPresent`. + enum: + - Always + - Never + - IfNotPresent + type: string + pullSecretRef: + description: |- + pullSecretRef references the secrets in the same namespace to be used for pulling images. + Image pull secrets are distinct from secrets because secrets + can be mounted in the pod, but image pull secrets are only accessed by `kubelet`. + More info: https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod + items: + type: string + type: array + required: + - application + - init + type: object + injectAnnotations: + additionalProperties: + type: string + description: |- + injectAnnotations are the annotations injected to the internal resources that CFK created. + The internal annotations are preserved and cannot be overridden. + For pod annotations, use `podTemplate.annotations`. + type: object + x-kubernetes-map-type: granular + injectLabels: + additionalProperties: + type: string + description: |- + injectLabels are the labels injected to the internal resources that CFK created. + The internal labels are preserved and cannot be overridden. + For pod labels, use `podTemplate.labels`. + type: object + x-kubernetes-map-type: granular + internalTopicReplicatorFactor: + description: internalTopicReplicationFactor specifies the replication + factor for internal topics. + format: int32 + type: integer + k8sClusterDomain: + description: |- + k8sClusterDomain specifies the configuration of the Kubernetes cluster domain. + The default is the `cluster.local` domain. + type: string + license: + description: license specifies the license configuration for the Confluent + Platform component. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + the license key is mounted. More info: + https://docs.confluent.io/operator/current/co-license.html#update-component-level-licenses + minLength: 1 + type: string + globalLicense: + description: globalLicense specifies whether the Confluent Platform + component shares the common global license. + type: boolean + secretRef: + description: |- + secretRef references the secret that provides the license for the Confluent Platform component. + More info: https://docs.confluent.io/operator/current/co-license.html#update-component-level-licenses + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + mail: + description: |- + mail specifies the settings that control the SMTP server and + account used when an alert triggers an email action. + properties: + authentication: + description: |- + authentication specifies the authentication for SMTP. SMP only supports basic authentication. + For other types of authentication, use the config overrides capability. + properties: + basic: + description: basic specifies the configuration for basic authentication. + properties: + debug: + description: debug enables the basic authentication debug + logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: OAuth specifies the configuration for OAuth authentication. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass the + basic credential through a directory path in the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to pass + the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication scheme for + the REST API client. Valid options are `basic` and `mtls`. + enum: + - basic + - mtls + - oauth + type: string + required: + - type + type: object + checkServerIdentity: + description: checkServerIdentity forces validation of server’s + certificate when using STARTTLS or SSL. + type: boolean + hostname: + description: hostname is the hostname of the outgoing SMTP server. + minLength: 1 + type: string + mailBounceAddress: + description: mailBounceAddress is the override for the `mailFrom` + config to send message. + minLength: 1 + type: string + mailFrom: + description: mailFrom is the originating address for emails sent + from the Control Center. + minLength: 1 + type: string + port: + description: port is the SMTP port open on the hostname. + format: int32 + type: integer + startTLSRequired: + description: startTLSRequired forces using STARTTLS. + type: boolean + required: + - hostname + type: object + metrics: + description: metrics specify the security settings for the metric + services. + properties: + authentication: + description: authentication specifies the authentication configuration + for the metrics. + properties: + type: + description: type specifies the metrics authentication method. + The valid option is `mtls`. + enum: + - mtls + type: string + required: + - type + type: object + prometheus: + description: prometheus specifies the configuration overrides + for the JMX-Prometheus exporter. + properties: + blacklist: + items: + type: string + type: array + rules: + items: + description: Rule defines the Prometheus Exporter rule override. + properties: + attrNameSnakeCase: + type: boolean + cache: + type: boolean + help: + minLength: 1 + type: string + labels: + additionalProperties: + type: string + type: object + x-kubernetes-map-type: granular + name: + minLength: 1 + type: string + pattern: + minLength: 1 + type: string + type: + minLength: 1 + type: string + value: + minLength: 1 + type: string + valueFactor: + anyOf: + - type: integer + - type: string + default: 1 + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: array + whitelist: + items: + type: string + type: array + type: object + tls: + description: tls specifies the TLS configuration for the metrics. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + monitoringKafkaClusters: + description: monitoringKafkaClusters specify the configurations for + the Kafka clusters that this Control Center monitors. + items: + description: MonitoringKafkaClusters defines the configuration of + the additional Kafka clusters the Control Center monitors. + properties: + authentication: + description: authentication defines the authentication for the + Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another way + to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim in + the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected issuer + in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of claim + in token for identifying the groups of subject in + the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout with + IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry backoff + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim in + JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap + endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + discovery: + description: discovery specifies the capability to discover + the Kafka cluster. + properties: + name: + description: name is the name of the Confluent Platform + component cluster. + type: string + namespace: + description: |- + namespace is where the Confluent Platform component is running. + The default value is the namespace where CFK is running. + type: string + secretRef: + description: secretRef is the name of the secret used to + discover the Confluent Platform component. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + name: + description: name defines the Kafka cluster name. + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + tls: + description: tls defines the client-side TLS setting for the + Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + required: + - name + type: object + type: array + mountedSecrets: + description: |- + mountedSecrets list the secrets injected to + the underlying statefulset configuration. The secret reference is mounted + in the default path `/mnt/secrets/`. The underlying resources + will follow the secret as a file configuration. + More info: https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets-as-files-from-a-pod. + A change to this setting will roll the cluster. + items: + description: |- + MountedSecrets provides a way to inject a custom secret to the underlying + statefulset. + properties: + keyItems: + description: keyItems are key and path names. + items: + description: Maps a string key to a path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + secretRef: + description: secretRef references the name of the secret. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + type: array + mountedVolumes: + description: |- + mountedVolumes list the custom volumes that need to be mounted into the + underlying statefulset. + A change to this setting will roll the cluster. + properties: + volumeMounts: + description: |- + volumeMounts specify the list of volume mounts for the pods in the + statefulset. + items: + description: VolumeMount describes a mounting of a Volume within + a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + description: |- + volumes specify the list of volumes that can be mounted into the pods + of statefulset. + items: + description: Volume represents a named volume in a pod that + may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + format: int32 + type: integer + readOnly: + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: boolean + volumeID: + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + required: + - volumeID + type: object + azureDisk: + description: azureDisk represents an Azure Data Disk mount + on the host and bind mount to the pod. + properties: + cachingMode: + description: 'cachingMode is the Host Caching mode: + None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the data disk in + the blob storage + type: string + diskURI: + description: diskURI is the URI of data disk in the + blob storage + type: string + fsType: + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: multiple + blob disks per storage account Dedicated: single + blob disk per storage account Managed: azure managed + data disk (only in managed availability set). defaults + to shared' + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: azureFile represents an Azure File Service + mount on the host and bind mount to the pod. + properties: + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of secret that + contains Azure Storage Account Name and Key + type: string + shareName: + description: shareName is the azure share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: cephFS represents a Ceph FS mount on the host + that shares a pod's lifetime + properties: + monitors: + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + items: + type: string + type: array + path: + description: 'path is Optional: Used as the mounted + root, rather than the full Ceph tree, default is /' + type: string + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: boolean + secretFile: + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + secretRef: + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + required: + - monitors + type: object + cinder: + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: boolean + secretRef: + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + description: csi (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers (Beta feature). + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward API about the + pod that should populate this volume + properties: + defaultMode: + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents information + to create the file containing the pod field + properties: + fieldRef: + description: 'Required: Selects a field of the + pod: only annotations, labels, name and namespace + are supported.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must not + be absolute or contain the ''..'' path. Must + be utf-8 encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + emptyDir: + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + properties: + medium: + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: 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]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. + properties: + volumeClaimTemplate: + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + + Required, must not be nil. + properties: + metadata: + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. + type: object + spec: + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim + are also valid here. + properties: + accessModes: + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + items: + type: string + type: array + dataSource: + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + 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. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any 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. + 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 + required: + - kind + - name + type: object + resources: + description: |- + resources represents the minimum resources the volume should have. + If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + selector: + description: selector is a label query over + volumes to consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + type: string + volumeAttributesClassName: + description: |- + volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. + If specified, the CSI driver will create or update the volume with the attributes defined + in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, + it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass + will be applied to the claim but it's not allowed to reset this field to empty string once it is set. + If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass + will be set by the persistentvolume controller if it exists. + If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be + set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource + exists. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#volumeattributesclass + (Alpha) Using this field requires the VolumeAttributesClass feature gate to be enabled. + type: string + volumeMode: + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding reference + to the PersistentVolume backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource that + is attached to a kubelet's host machine and then exposed + to the pod. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + lun: + description: 'lun is Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC target worldwide + names (WWNs)' + items: + type: string + type: array + wwids: + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. + items: + type: string + type: array + type: object + flexVolume: + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. + properties: + driver: + description: driver is the name of the driver to use + for this volume. + type: string + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this field holds + extra command options if any.' + type: object + readOnly: + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + description: flocker represents a Flocker volume attached + to a kubelet's host machine. This depends on the Flocker + control service being running + properties: + datasetName: + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of the dataset. + This is unique identifier of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + properties: + fsType: + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + format: int32 + type: integer + pdName: + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: boolean + required: + - pdName + type: object + gitRepo: + description: |- + gitRepo represents a git repository at a particular revision. + DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. + properties: + directory: + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash for the specified + revision. + type: string + required: + - repository + type: object + glusterfs: + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/glusterfs/README.md + properties: + endpoints: + description: |- + endpoints is the endpoint name that details Glusterfs topology. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + path: + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + readOnly: + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- + TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not + mount host directories as read/write. + properties: + path: + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + type: + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + required: + - path + type: object + iscsi: + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://examples.k8s.io/volumes/iscsi/README.md + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether support + iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether support + iSCSI Session CHAP authentication + type: boolean + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + initiatorName: + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified Name. + type: string + iscsiInterface: + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret for iSCSI + target and initiator authentication + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + nfs: + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + properties: + path: + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + readOnly: + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: boolean + server: + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + properties: + claimName: + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + type: string + readOnly: + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: photonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets host + machine + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies Photon Controller + persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: portworxVolume represents a portworx volume + attached and mounted on kubelets host machine + properties: + fsType: + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one resources secrets, + configmaps, and downward API + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + sources: + description: sources is the list of volume projections + items: + description: Projection that may be projected along + with other supported volume types + properties: + clusterTrustBundle: + description: |- + ClusterTrustBundle allows a pod to access the `.spec.trustBundle` field + of ClusterTrustBundle objects in an auto-updating file. + + + Alpha, gated by the ClusterTrustBundleProjection feature gate. + + + ClusterTrustBundle objects can either be selected by name, or by the + combination of signer name and a label selector. + + + Kubelet performs aggressive normalization of the PEM contents written + into the pod filesystem. Esoteric PEM features such as inter-block + comments and block headers are stripped. Certificates are deduplicated. + The ordering of certificates within the file is arbitrary, and Kubelet + may change the order over time. + properties: + labelSelector: + description: |- + Select all ClusterTrustBundles that match this label selector. Only has + effect if signerName is set. Mutually-exclusive with name. If unset, + interpreted as "match nothing". If set but empty, interpreted as "match + everything". + 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 + name: + description: |- + Select a single ClusterTrustBundle by object name. Mutually-exclusive + with signerName and labelSelector. + type: string + optional: + description: |- + If true, don't block pod startup if the referenced ClusterTrustBundle(s) + aren't available. If using name, then the named ClusterTrustBundle is + allowed not to exist. If using signerName, then the combination of + signerName and labelSelector is allowed to match zero + ClusterTrustBundles. + type: boolean + path: + description: Relative path from the volume + root to write the bundle. + type: string + signerName: + description: |- + Select all ClusterTrustBundles that match this signer name. + Mutually-exclusive with name. The contents of all selected + ClusterTrustBundles will be unified and deduplicated. + type: string + required: + - path + type: object + configMap: + description: configMap information about the configMap + data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional specify whether the + ConfigMap or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about the + downwardAPI data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a field + of the pod: only annotations, labels, + name and namespace are supported.' + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in terms + of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to + select in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file to + be created. Must not be absolute or + contain the ''..'' path. Must be utf-8 + encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env + vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + secret: + description: secret information about the secret + data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional field specify whether + the Secret or its key must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information + about the serviceAccountToken data to project + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + format: int64 + type: integer + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + description: quobyte represents a Quobyte mount on the host + that shares a pod's lifetime + properties: + group: + description: |- + group to map volume access to + Default is no group + type: string + readOnly: + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes + type: string + tenant: + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: |- + user to map volume access to + Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references an already + created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/rbd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + image: + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + keyring: + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + monitors: + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + items: + type: string + type: array + pool: + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: boolean + secretRef: + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + required: + - image + - monitors + type: object + scaleIO: + description: scaleIO represents a ScaleIO persistent volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". + type: string + gateway: + description: gateway is the host address of the ScaleIO + API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name of the ScaleIO + Protection Domain for the configured storage. + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + description: sslEnabled Flag enable/disable SSL communication + with Gateway, default false + type: boolean + storageMode: + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO Storage Pool + associated with the protection domain. + type: string + system: + description: system is the name of the storage system + as configured in ScaleIO. + type: string + volumeName: + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: optional field specify whether the Secret + or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + type: object + storageos: + description: storageOS represents a StorageOS volume attached + and mounted on Kubernetes nodes. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. + type: string + volumeNamespace: + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: vsphereVolume represents a vSphere volume attached + and mounted on kubelets host machine + properties: + fsType: + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage Policy Based + Management (SPBM) profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage Policy + Based Management (SPBM) profile name. + type: string + volumePath: + description: volumePath is the path that identifies + vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + required: + - volumeMounts + - volumes + type: object + name: + description: name is the Control Center cluster name. + type: string + oneReplicaPerNode: + description: |- + oneReplicaPerNode controls whether to run 1 pod per node using the pod anti-affinity capability. + Enabling this configuration in an existing cluster will roll the cluster. + type: boolean + pdb: + description: |- + configures PodDisruptionBudget for the Confluent Platform component. + by default PDB is configured based on pre-detemined formula. + properties: + enabled: + description: enabled specifies whether the PodDisruptionBudget + is enabled + type: boolean + maxUnavailable: + description: maxUnavailable is the maximum number of pods that + can be unavailable during the disruption. + format: int32 + type: integer + required: + - enabled + type: object + podTemplate: + description: podTemplate specifies the statefulset pod template configuration. + properties: + affinity: + description: |- + affinity specifies a group of affinity scheduling rules. + More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + type: array + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + annotations: + additionalProperties: + type: string + description: |- + annotations is a map of string key and value pairs stored with the resource and + may be set by external tools to store and retrieve arbitrary metadata. They + are not queryable and should be preserved when modifying objects. More + info: http://kubernetes.io/docs/user-guide/annotations. + type: object + x-kubernetes-map-type: granular + envVars: + description: |- + envVars contain environment variables to be injected into containers. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container. + 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 + labels: + additionalProperties: + type: string + description: |- + labels is a map of string key and value pairs that can be used to organize and categorize + (scope and select) objects. + More info: http://kubernetes.io/docs/user-guide/labels. + type: object + x-kubernetes-map-type: granular + podSecurityContext: + description: |- + PodSecurityContext holds pod-level security attributes and common container settings. + Some fields are also present in container.securityContext. Field values of + container.securityContext take precedence over field values of PodSecurityContext. + 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: + + + 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---- + + + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + 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 + type: object + 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. + 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: + + + 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 + required: + - type + type: object + 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. + items: + format: int64 + type: integer + type: array + 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. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + 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. + 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 + type: object + type: object + priorityClassName: + description: priorityClassName specifies the priority class for + the pod (if any). + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + probe: + description: probe contains the fields for standard Kubernetes + readiness/liveness probe configuration. + properties: + liveness: + description: |- + liveness configures the Kubernetes probe settings. The changes + will override the existing default configuration. + properties: + failureThreshold: + description: |- + failureThreshold is the minimum consecutive failures for the probe to be considered failed. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + initialDelaySeconds: + description: |- + initialDelaySeconds is the number of seconds after the container has started and before probes are initiated. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + path: + description: Path for the HTTP probe + type: string + periodSeconds: + description: |- + periodSeconds specifies how often to perform the probe. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + port: + description: Number of the port to access on the container + type: integer + successThreshold: + description: |- + successThreshold is the minimum consecutive successes for the probe to be considered successful after having failed. + The default values is `1`. Must be `1` for liveness and startup. The minimum value is `1`. + format: int32 + type: integer + timeoutSeconds: + description: |- + timeoutSeconds is the number of seconds after which the probe times out. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + type: object + readiness: + description: |- + readiness configures the Kubernetes probe setting. The changes + will override the existing default configuration. + properties: + failureThreshold: + description: |- + failureThreshold is the minimum consecutive failures for the probe to be considered failed. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + initialDelaySeconds: + description: |- + initialDelaySeconds is the number of seconds after the container has started and before probes are initiated. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + path: + description: Path for the HTTP probe + type: string + periodSeconds: + description: |- + periodSeconds specifies how often to perform the probe. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + port: + description: Number of the port to access on the container + type: integer + successThreshold: + description: |- + successThreshold is the minimum consecutive successes for the probe to be considered successful after having failed. + The default values is `1`. Must be `1` for liveness and startup. The minimum value is `1`. + format: int32 + type: integer + timeoutSeconds: + description: |- + timeoutSeconds is the number of seconds after which the probe times out. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + type: object + type: object + resources: + description: resources describe 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. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: |- + SecurityContext holds security configuration that will be applied to a container. + Some fields are present in both SecurityContext and PodSecurityContext. When both + are set, the values in SecurityContext take precedence. + 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. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + 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 + type: object + 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. + 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: + + + 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 + required: + - type + type: object + 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. + 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 + type: object + type: object + serviceAccountName: + description: |- + ServiceAccountName is the name of the service account used to run this pod. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account. + type: string + terminationGracePeriodSeconds: + description: terminationGracePeriodSeconds is the grace period + before the pod is deleted. + format: int64 + type: integer + tolerations: + description: |- + tolerations specify the pods to schedule onto the nodes with matching taints, using + the triple `` and the matching operator ``. + 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 + topologySpreadConstraints: + description: |- + topologySpreadConstraints describe how a group of pods ought to spread across topology domains. Scheduler will + schedule pods based on the constraints. All topologySpreadConstraints are ANDed. + items: + description: TopologySpreadConstraint specifies how to spread + matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + 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. + + + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + 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. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + + + This is a beta field and requires the MinDomainsInPodTopologySpread feature gate to be enabled (enabled by default). + format: int32 + type: integer + 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. + + + 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. + + + 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 + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + replicas: + description: |- + replicas is the desired number of replicas. + A change to this setting will roll the cluster. + format: int32 + type: integer + storageClass: + description: storageClass references the user-provided storage class. + properties: + name: + description: name is the storage class name. + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + telemetry: + description: telemetry specifies the Confluent telemetry reporter + configuration. + properties: + global: + description: |- + global allows disabling telemetry configuration. + If CFK is deployed with telemetry, this field is only + used to disable telemetry. The default value is `true` if + telemetry is enabled at the global level. + type: boolean + type: object + tls: + description: tls specifies the TLS configurations. + properties: + autoGeneratedCerts: + description: |- + autoGeneratedCerts specifies that the certificates are auto-generated based on + the CA key pair provided. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + fips: + description: |- + fips specifies the configuration of FIPS compliant Bouncy Castle type Java Keystores for the cp component's + TLS settings. TLS Secrets must have the keys keystore.bcfks, truststore.bcfks, and jksPassword.txt + properties: + enabled: + description: enabled specifies whether to enable the FIPS + configuration for cp components. + type: boolean + required: + - enabled + type: object + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing the + JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + required: + - dataVolumeCapacity + - image + type: object + status: + description: status defines the observed state of the Control Center cluster. + properties: + arbitraryData: + description: arbitraryData is the map for any arbitrary data associated + with this Confluent component. + x-kubernetes-preserve-unknown-fields: true + authorizationType: + description: authorizationType is the authorization type for this + Confluent component. + type: string + clusterName: + description: clusterName is the name of the Confluent Platform component + cluster. + type: string + clusterNamespace: + description: clusterNamespace is the namespace where the Confluent + Platform component cluster is running. + type: string + conditions: + description: conditions specify the latest available observations + of the current state. + items: + description: Condition represent the latest available observations + of the current state. + properties: + lastProbeTime: + description: lastProbeTime shows the last time the condition + was evaluated. + format: date-time + type: string + lastTransitionTime: + description: lastTransitionTime shows the last time the condition + was transitioned from one status to another. + format: date-time + type: string + message: + description: message shows a human-readable message with details + about the transition. + type: string + reason: + description: reason shows the reason for the last transition + of the condition. + type: string + status: + description: status shows the status of the condition, one of + `True`, `False`, or `Unknown`. + type: string + type: + description: type shows the condition type. + type: string + type: object + type: array + controlCenterName: + description: name is the name of the Control Center cluster. + type: string + currentReplicas: + description: currentReplicas is the number of currently running replicas. + format: int32 + type: integer + id: + description: id is the identifier of the Control Center cluster. + format: int32 + type: integer + internalSecrets: + description: |- + internalSecrets are internal secrets created + by CFK for this Confluent component. + items: + type: string + type: array + internalTopicNames: + description: internalTopicNames are the topics used by the component + for internal use. + items: + type: string + type: array + kafka: + description: kafka is the Kafka client side status for the Control + Center cluster. + properties: + authenticationType: + description: authenticationType describes the authentication method + for the Kafka cluster. + type: string + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap endpoint. + type: string + tls: + description: tls indicates whether TLS is enabled for the Kafka + dependency. + type: boolean + type: object + observedGeneration: + description: observedGeneration is the most recent generation observed + for this Confluent component. + format: int64 + type: integer + operatorVersion: + description: operatorVersion is the internal version of CFK. + type: string + phase: + description: |- + phase describes the state of the Confluent Platform component. This can either be 'PROVISIONING' + or 'RUNNING' + 'PROVISIONING' means the Confluent Platform component is currently getting deployed and not ready yet. + 'RUNNING' means the Confluent Platform component has been successfully deployed. + type: string + rbac: + description: rbac contains the RBAC-related status when RBAC is enabled. + properties: + clusterID: + description: clusterID specifies the id of the cluster. + type: string + internalRolebindings: + description: internalRolebindings specifies the internal rolebindings. + items: + type: string + type: array + type: object + readyReplicas: + description: readyReplicas is the number of currently ready replicas. + format: int32 + type: integer + replicas: + description: replicas is the number of replicas. + format: int32 + type: integer + restConfig: + description: restConfig is the REST API configuration of the Control + Center cluster. + properties: + advertisedExternalEndpoints: + description: advertisedExternalEndpoints specifies other advertised + endpoints used, especially for Kafka. + items: + type: string + type: array + authenticationType: + description: authenticationType shows the authentication type + configured by the listener. + type: string + externalAccessType: + description: externalAccessType shows the external access type + used for the listener. + type: string + externalEndpoint: + description: externalEndpoint specifies the external endpoint + to connect to the Confluent component cluster. + type: string + internalEndpoint: + description: internalEndpoint specifies the internal endpoint + to connect to the Confluent component cluster. + type: string + tls: + description: tls shows whether TLS is configured for the listener. + type: boolean + type: object + selector: + description: |- + selector gets the label selector of the child pod. + The Horizontal Pod Autoscaler(HPA) will scale using the label selector of the child pod. + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + scale: + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + status: {} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kafkarestclasses.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kafkarestclasses.yaml new file mode 100644 index 000000000..4eb8d72fc --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kafkarestclasses.yaml @@ -0,0 +1,557 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: kafkarestclasses.platform.confluent.io +spec: + group: platform.confluent.io + names: + categories: + - all + - confluent-platform + - confluent + kind: KafkaRestClass + listKind: KafkaRestClassList + plural: kafkarestclasses + shortNames: + - krc + - kafkarestclass + singular: kafkarestclass + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: KafkaRestClass is the schema for the Kafka REST 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: spec defines the desired state of the KafkaRestClass. + properties: + kafkaClusterRef: + description: kafkaClusterRef specifies the name of the Kafka cluster. + properties: + name: + description: name specifies the name of the Confluent Platform + component cluster. + type: string + namespace: + description: namespace specifies the namespace where the Confluent + Platform component cluster is running. + type: string + required: + - name + type: object + kafkaRest: + description: kafkaRest specifies the Kafka REST API configuration. + properties: + authentication: + description: authentication specifies the REST API authentication + mechanism. + properties: + basic: + description: basic specifies the basic authentication settings + for the REST API client. + properties: + debug: + description: debug enables the basic authentication debug + logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + bearer: + description: bearer specifies the bearer authentication settings + for the REST API client. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: oauth specifies the OAuth authentication settings + for the REST API client. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass the + basic credential through a directory path in the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to pass + the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the REST API authentication type. + Valid options are `basic`, `bearer`, `mtls` and `oauth`. + enum: + - basic + - bearer + - mtls + - oauth + type: string + required: + - type + type: object + endpoint: + description: endpoint specifies where Confluent REST API is running. + minLength: 1 + pattern: ^https?://.* + type: string + kafkaClusterID: + description: |- + kafkaClusterID specifies the id of Kafka cluster. + It takes precedence over using the Kafka REST API to get the cluster id. + minLength: 1 + type: string + tls: + description: "tls specifies the custom TLS structure for the application + resources,\n\t// e.g. connector, topic, schema, of the Confluent + Platform components.\n\t// +optional" + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer contains the directory path in the container where + `keystore.jks`, `truststore.jks`, `jksPassword.txt` keys are mounted. + minLength: 1 + type: string + jksPassword: + description: jksPassword specifies the secret name that contains + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef specifies the secret name that contains the certificates. + More info about certificates key/value format: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: object + secondaryKafkaClusterRef: + description: secondaryKafkaClusterRef specifies the name of the secondary + Kafka cluster when using centralized RBAC. + properties: + name: + description: name specifies the name of the Confluent Platform + component cluster. + type: string + namespace: + description: namespace specifies the namespace where the Confluent + Platform component cluster is running. + type: string + required: + - name + type: object + secondaryKafkaRest: + description: secondaryKafkaRest specifies the secondary Kafka REST + API configuration when using centralized RBAC. + properties: + authentication: + description: authentication specifies the REST API authentication + mechanism. + properties: + basic: + description: basic specifies the basic authentication settings + for the REST API client. + properties: + debug: + description: debug enables the basic authentication debug + logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + bearer: + description: bearer specifies the bearer authentication settings + for the REST API client. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: oauth specifies the OAuth authentication settings + for the REST API client. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass the + basic credential through a directory path in the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to pass + the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the REST API authentication type. + Valid options are `basic`, `bearer`, `mtls` and `oauth`. + enum: + - basic + - bearer + - mtls + - oauth + type: string + required: + - type + type: object + endpoint: + description: endpoint specifies where Confluent REST API is running. + minLength: 1 + pattern: ^https?://.* + type: string + kafkaClusterID: + description: |- + kafkaClusterID specifies the id of Kafka cluster. + It takes precedence over using the Kafka REST API to get the cluster id. + minLength: 1 + type: string + tls: + description: "tls specifies the custom TLS structure for the application + resources,\n\t// e.g. connector, topic, schema, of the Confluent + Platform components.\n\t// +optional" + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer contains the directory path in the container where + `keystore.jks`, `truststore.jks`, `jksPassword.txt` keys are mounted. + minLength: 1 + type: string + jksPassword: + description: jksPassword specifies the secret name that contains + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef specifies the secret name that contains the certificates. + More info about certificates key/value format: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: object + type: object + status: + description: status defines the observed state of the KafkaRestClass. + properties: + conditions: + description: conditions are the latest available observed state of + the kafkaRestClass. + items: + description: Condition represent the latest available observations + of the current state. + properties: + lastProbeTime: + description: lastProbeTime shows the last time the condition + was evaluated. + format: date-time + type: string + lastTransitionTime: + description: lastTransitionTime shows the last time the condition + was transitioned from one status to another. + format: date-time + type: string + message: + description: message shows a human-readable message with details + about the transition. + type: string + reason: + description: reason shows the reason for the last transition + of the condition. + type: string + status: + description: status shows the status of the condition, one of + `True`, `False`, or `Unknown`. + type: string + type: + description: type shows the condition type. + type: string + type: object + type: array + endpoint: + description: endpoint specifies the Kafka REST API / MDS endpoint. + type: string + kafkaClusterID: + description: |- + kafkaClusterID specifies the id of the Kafka cluster. + If using centralized RBAC and kafkaRestClass is for the secondary Kafka cluster, it will be the cluster id of the secondary Kafka cluster. + type: string + observedGeneration: + description: observedGeneration is the most recent generation observed + for this Confluent component. + format: int64 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kafkarestproxies.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kafkarestproxies.yaml new file mode 100644 index 000000000..d0a5ce5f6 --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kafkarestproxies.yaml @@ -0,0 +1,5834 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: kafkarestproxies.platform.confluent.io +spec: + group: platform.confluent.io + names: + categories: + - all + - confluent-platform + - confluent + kind: KafkaRestProxy + listKind: KafkaRestProxyList + plural: kafkarestproxies + shortNames: + - kafkarestproxy + - krp + singular: kafkarestproxy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.replicas + name: Replicas + type: string + - jsonPath: .status.readyReplicas + name: Ready + type: string + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.kafka.bootstrapEndpoint + name: Kafka + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: KafkaRestProxy is the schema for the Kafka REST Proxy 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: spec defines the desired state of the KafkaRestProxy cluster. + properties: + authentication: + description: authentication specifies the authentication configurations + for the KafkaRestProxy cluster. + properties: + basic: + description: basic specifies the configuration for basic authentication. + properties: + debug: + description: debug enables the basic authentication debug + logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: OAuth specifies the configuration for OAuth authentication. + properties: + configuration: + description: configuration specifies the OAuth server settings. + properties: + audience: + description: audience specifies the audience claim in + the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected issuer + in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of claim + in token for identifying the groups of subject in the + JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout with + IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry backoff + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff with + IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim in JWT + to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass the basic + credential through a directory path in the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to pass the + required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication scheme for the + REST API server. Valid options are `basic` and `mtls`. + enum: + - basic + - mtls + - oauth + type: string + required: + - type + type: object + authorization: + description: authorization specifies the RBAC configuration for the + KafkaRestProxy cluster. + properties: + kafkaRestClassRef: + description: |- + kafkaRestClassRef references the KafkaRestClass + which specifies the Kafka REST API connection configuration. + properties: + name: + description: name specifies the name of the KafkaRestClass + application resource. + minLength: 1 + type: string + namespace: + description: namespace specifies the namespace of the KafkaRestClass. + type: string + required: + - name + type: object + type: + description: type specifies the client-side authorization type. + The valid option is `rbac`. + enum: + - rbac + type: string + required: + - type + type: object + configOverrides: + description: |- + configOverrides specifies the configs to override the server, JVM, Log4j properties for the KafkaRestProxy cluster. + A change will roll the cluster. + properties: + jvm: + description: |- + jvm is a list of JVM configuration supported by the Confluent Platform component. + This will either add or update the existing configuration. + items: + type: string + type: array + log4j: + description: |- + log4j is a list of Log4J configuration supported by the Confluent Platform component. + This will either add or update the existing configuration. + items: + type: string + type: array + server: + description: |- + server is a list of server configuration supported by the Confluent Platform component. + This will either add or update existing configuration. + items: + type: string + type: array + type: object + dependencies: + description: dependencies specifies the dependency configurations + for Kafka, Interceptor, Schema Registry, and the MDS. + properties: + interceptor: + description: interceptor specifies the interceptor dependency + configuration. + properties: + configs: + description: |- + configs describe the configurations for the Confluent Platform interceptor. + The config override feature can be used to pass the configuration settings. + items: + type: string + type: array + consumer: + description: |- + consumer specifies the consumer configuration for the interceptor. If not + configured, it uses the Kafka dependency configuration. + properties: + authentication: + description: authentication defines the authentication + for the Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another + way to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap + endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + discovery: + description: discovery specifies the capability to discover + the Kafka cluster. + properties: + name: + description: name is the name of the Confluent Platform + component cluster. + type: string + namespace: + description: |- + namespace is where the Confluent Platform component is running. + The default value is the namespace where CFK is running. + type: string + secretRef: + description: secretRef is the name of the secret used + to discover the Confluent Platform component. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + tls: + description: tls defines the client-side TLS setting for + the Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + enabled: + description: enabled indicates whether the Confluent Platform + interceptor is enabled or disabled. + type: boolean + producer: + description: |- + producer specifies the producer configuration for the interceptor. If not + configured, it uses the Kafka dependency configuration. + properties: + authentication: + description: authentication defines the authentication + for the Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another + way to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap + endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + discovery: + description: discovery specifies the capability to discover + the Kafka cluster. + properties: + name: + description: name is the name of the Confluent Platform + component cluster. + type: string + namespace: + description: |- + namespace is where the Confluent Platform component is running. + The default value is the namespace where CFK is running. + type: string + secretRef: + description: secretRef is the name of the secret used + to discover the Confluent Platform component. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + tls: + description: tls defines the client-side TLS setting for + the Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + publishMs: + type: integer + required: + - enabled + type: object + kafka: + description: kafka specifies the Kafka dependency configuration. + properties: + authentication: + description: authentication defines the authentication for + the Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another way + to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap + endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + discovery: + description: discovery specifies the capability to discover + the Kafka cluster. + properties: + name: + description: name is the name of the Confluent Platform + component cluster. + type: string + namespace: + description: |- + namespace is where the Confluent Platform component is running. + The default value is the namespace where CFK is running. + type: string + secretRef: + description: secretRef is the name of the secret used + to discover the Confluent Platform component. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + tls: + description: tls defines the client-side TLS setting for the + Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + mds: + description: mds specifies the MDS dependencies configuration. + properties: + authentication: + description: authentication specifies the client side authentication + configuration for the MDS. + properties: + bearer: + description: bearer specifies the bearer authentication + settings. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: oauth specifies the OAuth authentication + settings. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass + the basic credential through a directory path in + the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to + pass the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication method + for the MDS. The valid option is `bearer`, `oauth`. + enum: + - bearer + - oauth + type: string + required: + - type + type: object + endpoint: + description: endpoint specifies the MDS endpoint. + minLength: 1 + pattern: ^https?://.* + type: string + ssoProtocol: + description: sso protocol, valid options are ldap and oidc. + enum: + - ldap + - oidc + type: string + tls: + description: ClientTLSConfig specifies the TLS configuration + for the Confluent component (dependencies, listeners). + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + tokenKeyPair: + description: tokenKeyPair specifies the token keypair to configure + the MDS. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer defines the directory path in the container + where the MDS token key pair are mounted. + minLength: 1 + type: string + encryptedTokenKey: + description: |- + EncryptedTokenKey boolean value indicating whether the tokenKeypair(private used for signing) is encrypted using a passphrase. If true, cfk + operator will look for a file named mdsTokenKeyPassphrase.txt containing key value pair + mdsTokenKeyPassphrase=. Relevant only for mds server. Ignored if set for a client configuration. + type: boolean + secretRef: + description: secretRef references the name of the secret + that contains the MDS token key pair. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + required: + - authentication + - endpoint + - tokenKeyPair + type: object + schemaRegistry: + description: schemaRegistry specifies the Schema Registry dependency + configuration. + properties: + authentication: + description: authentication specifies the authentication for + the Schema Registry cluster. + properties: + basic: + description: basic specifies the configuration for basic + authentication. + properties: + debug: + description: debug enables the basic authentication + debug logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: OAuth specifies the configuration for OAuth + authentication. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass + the basic credential through a directory path in + the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to + pass the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication scheme + for the REST API client. Valid options are `basic` and + `mtls`. + enum: + - basic + - mtls + - oauth + type: string + required: + - type + type: object + tls: + description: tls defines the client-side TLS setting for the + Schema Registry cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + url: + description: url specifies the URL endpoint of the Schema + Registry cluster. + minLength: 1 + pattern: ^https?://.* + type: string + required: + - url + type: object + type: object + externalAccess: + description: externalAccess specifies the external access configuration. + properties: + loadBalancer: + description: loadBalancer specifies the configuration to create + a Kubernetes load balancer service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and value + pairs. It specifies Kubernetes annotations for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain is the domain name of the component cluster. + minLength: 1 + type: string + externalTrafficPolicy: + description: externalTrafficPolicy specifies the external + traffic policy for the service. Valid options are `Local` + and `Cluster`. + enum: + - Local + - Cluster + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value pairs. + It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + loadBalancerSourceRanges: + description: loadBalancerSourceRanges specify the source ranges. + items: + type: string + type: array + port: + description: |- + port specifies the external port for the client consumption. + If not configured, the same internal/external port is configured for the component. + Information about the port can be retrieved through the status API. + format: int32 + type: integer + prefix: + description: |- + prefix specify the prefix for the given domain. + The default value is the name of the cluster. + minLength: 1 + type: string + servicePorts: + description: servicePorts specify the user-provided service + port(s). + items: + description: ServicePort contains information on service's + port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations of Client + IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - domain + type: object + nodePort: + description: nodePort specifies the configuration to create a + Kubernetes node port service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to `://:, where`podId` starts from `0` to `replicaCount - 1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and value + pairs. It specifies Kubernetes annotations for this service. + type: object + x-kubernetes-map-type: granular + externalTrafficPolicy: + description: |- + externalTrafficPolicy specifies the external traffic policy for the service. + Valid options are `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + host: + description: host defines the host name of the cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value pairs. + It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + nodePortOffset: + description: |- + nodePortOffset specifies the starting offset of the node ports. The port numbers go in ascending order with respect + to the replicas count. + NodePort service creation fails if the node port is not in the range supported by the Kubernetes API server. + The default Kubernetes Node Port range is `30000` - `32762`. + format: int32 + minimum: 0 + type: integer + servicePorts: + description: |- + servicePorts specify user-provided service port(s). + For Kafka with the nodePort type, this setting is only applied to Kafka bootstrap service. + items: + description: ServicePort contains information on service's + port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations of Client + IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - host + - nodePortOffset + type: object + route: + description: route specifies the configuration to create a route + service in OpenShift. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://-http-external.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and value + pairs. It specifies Kubernetes annotations for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain specifies the domain name of the Confluent + component cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value pairs. + It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + prefix: + description: |- + prefix specifies the component prefix when configured for the domain. + The default value is the name of the cluster. + minLength: 1 + type: string + wildcardPolicy: + description: |- + wildcardPolicy allows you to define a route that covers all hosts within a domain. Valid options are `Subdomain` and `None`. + The default value is `None`. + enum: + - Subdomain + - None + type: string + required: + - domain + type: object + type: + description: |- + type specifies the Kubernetes external service for the component. + Valid options are `loadBalancer`, `nodePort`, and `route`. + enum: + - loadBalancer + - nodePort + - route + minLength: 1 + type: string + required: + - type + type: object + headlessService: + description: headlessService specifies the configuration of the Kubernetes + headless service. + properties: + annotations: + additionalProperties: + type: string + description: |- + annotations is a map of string key and value pairs. + It specifies the annotations to be added to the CFK-created headless service. + These annotations are merged with the injectAnnotations and take precedence. + type: object + x-kubernetes-map-type: granular + labels: + additionalProperties: + type: string + description: |- + labels is a map of string key and value pairs. + It specifies the labels to be added to the CFK-created headless service. + These labels are merged with the injectLabels and take precedence. + type: object + x-kubernetes-map-type: granular + publishNotReadyAddresses: + description: |- + publishNotReadyAddresses specifies the publishNotReadyAddresses field. + For Kafka, this value must be true. The default value is true. + type: boolean + type: object + image: + description: |- + image specifies the application and the init docker image configurations. + A change to this setting will roll the cluster. + properties: + application: + description: |- + application is the Docker image name of the application. Specify + `//:`. + pattern: .+:.+ + type: string + init: + description: |- + init is the init-container name. Specify + `//:`. + pattern: .+:.+ + type: string + pullPolicy: + description: |- + pullPolicy is the policy for pulling images. Valid options are `Always`, `Never`, and `IfNotPresent`. + The default value is `IfNotPresent`. + enum: + - Always + - Never + - IfNotPresent + type: string + pullSecretRef: + description: |- + pullSecretRef references the secrets in the same namespace to be used for pulling images. + Image pull secrets are distinct from secrets because secrets + can be mounted in the pod, but image pull secrets are only accessed by `kubelet`. + More info: https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod + items: + type: string + type: array + required: + - application + - init + type: object + injectAnnotations: + additionalProperties: + type: string + description: |- + injectAnnotations are the annotations injected to the internal resources that CFK created. + The internal annotations are preserved and cannot be overridden. + For pod annotations, use `podTemplate.annotations`. + type: object + x-kubernetes-map-type: granular + injectLabels: + additionalProperties: + type: string + description: |- + injectLabels are the labels injected to the internal resources that CFK created. + The internal labels are preserved and cannot be overridden. + For pod labels, use `podTemplate.labels`. + type: object + x-kubernetes-map-type: granular + k8sClusterDomain: + description: |- + k8sClusterDomain specifies the configuration of the Kubernetes cluster domain. + The default is the `cluster.local` domain. + type: string + license: + description: license specifies the license configuration for the Confluent + Platform component. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + the license key is mounted. More info: + https://docs.confluent.io/operator/current/co-license.html#update-component-level-licenses + minLength: 1 + type: string + globalLicense: + description: globalLicense specifies whether the Confluent Platform + component shares the common global license. + type: boolean + secretRef: + description: |- + secretRef references the secret that provides the license for the Confluent Platform component. + More info: https://docs.confluent.io/operator/current/co-license.html#update-component-level-licenses + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + metrics: + description: metrics specify the security settings for the metric + services. + properties: + authentication: + description: authentication specifies the authentication configuration + for the metrics. + properties: + type: + description: type specifies the metrics authentication method. + The valid option is `mtls`. + enum: + - mtls + type: string + required: + - type + type: object + prometheus: + description: prometheus specifies the configuration overrides + for the JMX-Prometheus exporter. + properties: + blacklist: + items: + type: string + type: array + rules: + items: + description: Rule defines the Prometheus Exporter rule override. + properties: + attrNameSnakeCase: + type: boolean + cache: + type: boolean + help: + minLength: 1 + type: string + labels: + additionalProperties: + type: string + type: object + x-kubernetes-map-type: granular + name: + minLength: 1 + type: string + pattern: + minLength: 1 + type: string + type: + minLength: 1 + type: string + value: + minLength: 1 + type: string + valueFactor: + anyOf: + - type: integer + - type: string + default: 1 + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: array + whitelist: + items: + type: string + type: array + type: object + tls: + description: tls specifies the TLS configuration for the metrics. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + mountedSecrets: + description: |- + mountedSecrets list the secrets injected to + the underlying statefulset configuration. The secret reference is mounted + in the default path `/mnt/secrets/`. The underlying resources + will follow the secret as a file configuration. + More info: https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets-as-files-from-a-pod. + A change to this setting will roll the cluster. + items: + description: |- + MountedSecrets provides a way to inject a custom secret to the underlying + statefulset. + properties: + keyItems: + description: keyItems are key and path names. + items: + description: Maps a string key to a path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + secretRef: + description: secretRef references the name of the secret. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + type: array + mountedVolumes: + description: |- + mountedVolumes list the custom volumes that need to be mounted into the + underlying statefulset. + A change to this setting will roll the cluster. + properties: + volumeMounts: + description: |- + volumeMounts specify the list of volume mounts for the pods in the + statefulset. + items: + description: VolumeMount describes a mounting of a Volume within + a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + description: |- + volumes specify the list of volumes that can be mounted into the pods + of statefulset. + items: + description: Volume represents a named volume in a pod that + may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + format: int32 + type: integer + readOnly: + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: boolean + volumeID: + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + required: + - volumeID + type: object + azureDisk: + description: azureDisk represents an Azure Data Disk mount + on the host and bind mount to the pod. + properties: + cachingMode: + description: 'cachingMode is the Host Caching mode: + None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the data disk in + the blob storage + type: string + diskURI: + description: diskURI is the URI of data disk in the + blob storage + type: string + fsType: + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: multiple + blob disks per storage account Dedicated: single + blob disk per storage account Managed: azure managed + data disk (only in managed availability set). defaults + to shared' + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: azureFile represents an Azure File Service + mount on the host and bind mount to the pod. + properties: + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of secret that + contains Azure Storage Account Name and Key + type: string + shareName: + description: shareName is the azure share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: cephFS represents a Ceph FS mount on the host + that shares a pod's lifetime + properties: + monitors: + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + items: + type: string + type: array + path: + description: 'path is Optional: Used as the mounted + root, rather than the full Ceph tree, default is /' + type: string + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: boolean + secretFile: + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + secretRef: + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + required: + - monitors + type: object + cinder: + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: boolean + secretRef: + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + description: csi (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers (Beta feature). + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward API about the + pod that should populate this volume + properties: + defaultMode: + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents information + to create the file containing the pod field + properties: + fieldRef: + description: 'Required: Selects a field of the + pod: only annotations, labels, name and namespace + are supported.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must not + be absolute or contain the ''..'' path. Must + be utf-8 encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + emptyDir: + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + properties: + medium: + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: 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]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. + properties: + volumeClaimTemplate: + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + + Required, must not be nil. + properties: + metadata: + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. + type: object + spec: + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim + are also valid here. + properties: + accessModes: + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + items: + type: string + type: array + dataSource: + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + 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. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any 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. + 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 + required: + - kind + - name + type: object + resources: + description: |- + resources represents the minimum resources the volume should have. + If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + selector: + description: selector is a label query over + volumes to consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + type: string + volumeAttributesClassName: + description: |- + volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. + If specified, the CSI driver will create or update the volume with the attributes defined + in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, + it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass + will be applied to the claim but it's not allowed to reset this field to empty string once it is set. + If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass + will be set by the persistentvolume controller if it exists. + If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be + set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource + exists. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#volumeattributesclass + (Alpha) Using this field requires the VolumeAttributesClass feature gate to be enabled. + type: string + volumeMode: + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding reference + to the PersistentVolume backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource that + is attached to a kubelet's host machine and then exposed + to the pod. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + lun: + description: 'lun is Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC target worldwide + names (WWNs)' + items: + type: string + type: array + wwids: + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. + items: + type: string + type: array + type: object + flexVolume: + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. + properties: + driver: + description: driver is the name of the driver to use + for this volume. + type: string + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this field holds + extra command options if any.' + type: object + readOnly: + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + description: flocker represents a Flocker volume attached + to a kubelet's host machine. This depends on the Flocker + control service being running + properties: + datasetName: + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of the dataset. + This is unique identifier of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + properties: + fsType: + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + format: int32 + type: integer + pdName: + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: boolean + required: + - pdName + type: object + gitRepo: + description: |- + gitRepo represents a git repository at a particular revision. + DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. + properties: + directory: + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash for the specified + revision. + type: string + required: + - repository + type: object + glusterfs: + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/glusterfs/README.md + properties: + endpoints: + description: |- + endpoints is the endpoint name that details Glusterfs topology. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + path: + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + readOnly: + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- + TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not + mount host directories as read/write. + properties: + path: + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + type: + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + required: + - path + type: object + iscsi: + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://examples.k8s.io/volumes/iscsi/README.md + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether support + iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether support + iSCSI Session CHAP authentication + type: boolean + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + initiatorName: + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified Name. + type: string + iscsiInterface: + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret for iSCSI + target and initiator authentication + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + nfs: + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + properties: + path: + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + readOnly: + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: boolean + server: + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + properties: + claimName: + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + type: string + readOnly: + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: photonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets host + machine + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies Photon Controller + persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: portworxVolume represents a portworx volume + attached and mounted on kubelets host machine + properties: + fsType: + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one resources secrets, + configmaps, and downward API + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + sources: + description: sources is the list of volume projections + items: + description: Projection that may be projected along + with other supported volume types + properties: + clusterTrustBundle: + description: |- + ClusterTrustBundle allows a pod to access the `.spec.trustBundle` field + of ClusterTrustBundle objects in an auto-updating file. + + + Alpha, gated by the ClusterTrustBundleProjection feature gate. + + + ClusterTrustBundle objects can either be selected by name, or by the + combination of signer name and a label selector. + + + Kubelet performs aggressive normalization of the PEM contents written + into the pod filesystem. Esoteric PEM features such as inter-block + comments and block headers are stripped. Certificates are deduplicated. + The ordering of certificates within the file is arbitrary, and Kubelet + may change the order over time. + properties: + labelSelector: + description: |- + Select all ClusterTrustBundles that match this label selector. Only has + effect if signerName is set. Mutually-exclusive with name. If unset, + interpreted as "match nothing". If set but empty, interpreted as "match + everything". + 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 + name: + description: |- + Select a single ClusterTrustBundle by object name. Mutually-exclusive + with signerName and labelSelector. + type: string + optional: + description: |- + If true, don't block pod startup if the referenced ClusterTrustBundle(s) + aren't available. If using name, then the named ClusterTrustBundle is + allowed not to exist. If using signerName, then the combination of + signerName and labelSelector is allowed to match zero + ClusterTrustBundles. + type: boolean + path: + description: Relative path from the volume + root to write the bundle. + type: string + signerName: + description: |- + Select all ClusterTrustBundles that match this signer name. + Mutually-exclusive with name. The contents of all selected + ClusterTrustBundles will be unified and deduplicated. + type: string + required: + - path + type: object + configMap: + description: configMap information about the configMap + data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional specify whether the + ConfigMap or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about the + downwardAPI data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a field + of the pod: only annotations, labels, + name and namespace are supported.' + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in terms + of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to + select in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file to + be created. Must not be absolute or + contain the ''..'' path. Must be utf-8 + encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env + vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + secret: + description: secret information about the secret + data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional field specify whether + the Secret or its key must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information + about the serviceAccountToken data to project + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + format: int64 + type: integer + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + description: quobyte represents a Quobyte mount on the host + that shares a pod's lifetime + properties: + group: + description: |- + group to map volume access to + Default is no group + type: string + readOnly: + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes + type: string + tenant: + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: |- + user to map volume access to + Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references an already + created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/rbd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + image: + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + keyring: + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + monitors: + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + items: + type: string + type: array + pool: + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: boolean + secretRef: + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + required: + - image + - monitors + type: object + scaleIO: + description: scaleIO represents a ScaleIO persistent volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". + type: string + gateway: + description: gateway is the host address of the ScaleIO + API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name of the ScaleIO + Protection Domain for the configured storage. + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + description: sslEnabled Flag enable/disable SSL communication + with Gateway, default false + type: boolean + storageMode: + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO Storage Pool + associated with the protection domain. + type: string + system: + description: system is the name of the storage system + as configured in ScaleIO. + type: string + volumeName: + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: optional field specify whether the Secret + or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + type: object + storageos: + description: storageOS represents a StorageOS volume attached + and mounted on Kubernetes nodes. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. + type: string + volumeNamespace: + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: vsphereVolume represents a vSphere volume attached + and mounted on kubelets host machine + properties: + fsType: + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage Policy Based + Management (SPBM) profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage Policy + Based Management (SPBM) profile name. + type: string + volumePath: + description: volumePath is the path that identifies + vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + required: + - volumeMounts + - volumes + type: object + oneReplicaPerNode: + description: |- + oneReplicaPerNode controls whether to run 1 pod per node using the pod anti-affinity capability. + Enabling this configuration in an existing cluster will roll the cluster. + type: boolean + pdb: + description: |- + configures PodDisruptionBudget for the Confluent Platform component. + by default PDB is configured based on pre-detemined formula. + properties: + enabled: + description: enabled specifies whether the PodDisruptionBudget + is enabled + type: boolean + maxUnavailable: + description: maxUnavailable is the maximum number of pods that + can be unavailable during the disruption. + format: int32 + type: integer + required: + - enabled + type: object + podTemplate: + description: podTemplate specifies the statefulset pod template configuration. + properties: + affinity: + description: |- + affinity specifies a group of affinity scheduling rules. + More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + type: array + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + annotations: + additionalProperties: + type: string + description: |- + annotations is a map of string key and value pairs stored with the resource and + may be set by external tools to store and retrieve arbitrary metadata. They + are not queryable and should be preserved when modifying objects. More + info: http://kubernetes.io/docs/user-guide/annotations. + type: object + x-kubernetes-map-type: granular + envVars: + description: |- + envVars contain environment variables to be injected into containers. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container. + 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 + labels: + additionalProperties: + type: string + description: |- + labels is a map of string key and value pairs that can be used to organize and categorize + (scope and select) objects. + More info: http://kubernetes.io/docs/user-guide/labels. + type: object + x-kubernetes-map-type: granular + podSecurityContext: + description: |- + PodSecurityContext holds pod-level security attributes and common container settings. + Some fields are also present in container.securityContext. Field values of + container.securityContext take precedence over field values of PodSecurityContext. + 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: + + + 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---- + + + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + 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 + type: object + 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. + 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: + + + 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 + required: + - type + type: object + 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. + items: + format: int64 + type: integer + type: array + 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. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + 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. + 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 + type: object + type: object + priorityClassName: + description: priorityClassName specifies the priority class for + the pod (if any). + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + probe: + description: probe contains the fields for standard Kubernetes + readiness/liveness probe configuration. + properties: + liveness: + description: |- + liveness configures the Kubernetes probe settings. The changes + will override the existing default configuration. + properties: + failureThreshold: + description: |- + failureThreshold is the minimum consecutive failures for the probe to be considered failed. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + initialDelaySeconds: + description: |- + initialDelaySeconds is the number of seconds after the container has started and before probes are initiated. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + path: + description: Path for the HTTP probe + type: string + periodSeconds: + description: |- + periodSeconds specifies how often to perform the probe. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + port: + description: Number of the port to access on the container + type: integer + successThreshold: + description: |- + successThreshold is the minimum consecutive successes for the probe to be considered successful after having failed. + The default values is `1`. Must be `1` for liveness and startup. The minimum value is `1`. + format: int32 + type: integer + timeoutSeconds: + description: |- + timeoutSeconds is the number of seconds after which the probe times out. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + type: object + readiness: + description: |- + readiness configures the Kubernetes probe setting. The changes + will override the existing default configuration. + properties: + failureThreshold: + description: |- + failureThreshold is the minimum consecutive failures for the probe to be considered failed. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + initialDelaySeconds: + description: |- + initialDelaySeconds is the number of seconds after the container has started and before probes are initiated. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + path: + description: Path for the HTTP probe + type: string + periodSeconds: + description: |- + periodSeconds specifies how often to perform the probe. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + port: + description: Number of the port to access on the container + type: integer + successThreshold: + description: |- + successThreshold is the minimum consecutive successes for the probe to be considered successful after having failed. + The default values is `1`. Must be `1` for liveness and startup. The minimum value is `1`. + format: int32 + type: integer + timeoutSeconds: + description: |- + timeoutSeconds is the number of seconds after which the probe times out. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + type: object + type: object + resources: + description: resources describe 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. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: |- + SecurityContext holds security configuration that will be applied to a container. + Some fields are present in both SecurityContext and PodSecurityContext. When both + are set, the values in SecurityContext take precedence. + 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. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + 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 + type: object + 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. + 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: + + + 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 + required: + - type + type: object + 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. + 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 + type: object + type: object + serviceAccountName: + description: |- + ServiceAccountName is the name of the service account used to run this pod. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account. + type: string + terminationGracePeriodSeconds: + description: terminationGracePeriodSeconds is the grace period + before the pod is deleted. + format: int64 + type: integer + tolerations: + description: |- + tolerations specify the pods to schedule onto the nodes with matching taints, using + the triple `` and the matching operator ``. + 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 + topologySpreadConstraints: + description: |- + topologySpreadConstraints describe how a group of pods ought to spread across topology domains. Scheduler will + schedule pods based on the constraints. All topologySpreadConstraints are ANDed. + items: + description: TopologySpreadConstraint specifies how to spread + matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + 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. + + + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + 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. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + + + This is a beta field and requires the MinDomainsInPodTopologySpread feature gate to be enabled (enabled by default). + format: int32 + type: integer + 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. + + + 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. + + + 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 + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + replicas: + description: |- + replicas is the desired number of replicas. + A change to this setting will roll the cluster. + format: int32 + type: integer + telemetry: + description: telemetry specifies the Confluent telemetry reporter + configuration. + properties: + global: + description: |- + global allows disabling telemetry configuration. + If CFK is deployed with telemetry, this field is only + used to disable telemetry. The default value is `true` if + telemetry is enabled at the global level. + type: boolean + type: object + tls: + description: tls specifies the TLS configurations for the KafkaRestProxy + cluster. + properties: + autoGeneratedCerts: + description: |- + autoGeneratedCerts specifies that the certificates are auto-generated based on + the CA key pair provided. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + fips: + description: |- + fips specifies the configuration of FIPS compliant Bouncy Castle type Java Keystores for the cp component's + TLS settings. TLS Secrets must have the keys keystore.bcfks, truststore.bcfks, and jksPassword.txt + properties: + enabled: + description: enabled specifies whether to enable the FIPS + configuration for cp components. + type: boolean + required: + - enabled + type: object + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing the + JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + required: + - image + type: object + status: + description: status defines the observed state of the KafkaRestProxy cluster. + properties: + arbitraryData: + description: arbitraryData is the map for any arbitrary data associated + with this Confluent component. + x-kubernetes-preserve-unknown-fields: true + authorizationType: + description: authorizationType is the authorization type for this + Confluent component. + type: string + clusterName: + description: clusterName is the name of the Confluent Platform component + cluster. + type: string + clusterNamespace: + description: clusterNamespace is the namespace where the Confluent + Platform component cluster is running. + type: string + conditions: + description: conditions specify the latest available observations + of the current state. + items: + description: Condition represent the latest available observations + of the current state. + properties: + lastProbeTime: + description: lastProbeTime shows the last time the condition + was evaluated. + format: date-time + type: string + lastTransitionTime: + description: lastTransitionTime shows the last time the condition + was transitioned from one status to another. + format: date-time + type: string + message: + description: message shows a human-readable message with details + about the transition. + type: string + reason: + description: reason shows the reason for the last transition + of the condition. + type: string + status: + description: status shows the status of the condition, one of + `True`, `False`, or `Unknown`. + type: string + type: + description: type shows the condition type. + type: string + type: object + type: array + currentReplicas: + description: currentReplicas is the number of currently running replicas. + format: int32 + type: integer + internalSecrets: + description: |- + internalSecrets are internal secrets created + by CFK for this Confluent component. + items: + type: string + type: array + internalTopicNames: + description: internalTopicNames are the topics used by the component + for internal use. + items: + type: string + type: array + kafka: + description: kafka is the Kafka client side status for the KafkaRestProxy + cluster. + properties: + authenticationType: + description: authenticationType describes the authentication method + for the Kafka cluster. + type: string + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap endpoint. + type: string + tls: + description: tls indicates whether TLS is enabled for the Kafka + dependency. + type: boolean + type: object + metricPrefix: + description: metricPrefix is the prefix for the JMX metric of the + KafkaRestProxy. + type: string + observedGeneration: + description: observedGeneration is the most recent generation observed + for this Confluent component. + format: int64 + type: integer + operatorVersion: + description: operatorVersion is the internal version of CFK. + type: string + phase: + description: |- + phase describes the state of the Confluent Platform component. This can either be 'PROVISIONING' + or 'RUNNING' + 'PROVISIONING' means the Confluent Platform component is currently getting deployed and not ready yet. + 'RUNNING' means the Confluent Platform component has been successfully deployed. + type: string + rbac: + description: rbac contains the RBAC-related status when RBAC is enabled. + properties: + clusterID: + description: clusterID specifies the id of the cluster. + type: string + internalRolebindings: + description: internalRolebindings specifies the internal rolebindings. + items: + type: string + type: array + type: object + readyReplicas: + description: readyReplicas is the number of currently ready replicas. + format: int32 + type: integer + replicas: + description: replicas is the number of replicas. + format: int32 + type: integer + restConfig: + description: restConfig is the REST API configuration of the KafkaRestProxy. + properties: + advertisedExternalEndpoints: + description: advertisedExternalEndpoints specifies other advertised + endpoints used, especially for Kafka. + items: + type: string + type: array + authenticationType: + description: authenticationType shows the authentication type + configured by the listener. + type: string + externalAccessType: + description: externalAccessType shows the external access type + used for the listener. + type: string + externalEndpoint: + description: externalEndpoint specifies the external endpoint + to connect to the Confluent component cluster. + type: string + internalEndpoint: + description: internalEndpoint specifies the internal endpoint + to connect to the Confluent component cluster. + type: string + tls: + description: tls shows whether TLS is configured for the listener. + type: boolean + type: object + selector: + description: |- + selector gets the label selector of the child pod. + The Horizontal Pod Autoscaler(HPA) will scale using the label selector of the child pod. + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + scale: + labelSelectorPath: .status.selector + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + status: {} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kafkas.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kafkas.yaml new file mode 100644 index 000000000..880eeed0f --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kafkas.yaml @@ -0,0 +1,10942 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: kafkas.platform.confluent.io +spec: + group: platform.confluent.io + names: + categories: + - all + - confluent-platform + - confluent + kind: Kafka + listKind: KafkaList + plural: kafkas + shortNames: + - kafka + - broker + singular: kafka + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.replicas + name: Replicas + type: string + - jsonPath: .status.readyReplicas + name: Ready + type: string + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.zookeeperConnect + name: Zookeeper + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: Kafka is the schema for the Kafka 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: spec defines the desired state of the Kafka cluster. + properties: + authorization: + description: authorization specifies the authorization configuration. + properties: + superUsers: + description: |- + superUsers specify the super users to give the admin privilege on the Kafka Cluster. + This list takes the format as `User:` + items: + type: string + type: array + type: + description: type specifies the authorization type. The valid + options are `rbac` and `simple`. + enum: + - rbac + - simple + type: string + required: + - type + type: object + configOverrides: + description: configOverrides specifies the configs to override the + server, JVM, Log4j properties for the Kafka cluster. + properties: + jvm: + description: |- + jvm is a list of JVM configuration supported by the Confluent Platform component. + This will either add or update the existing configuration. + items: + type: string + type: array + log4j: + description: |- + log4j is a list of Log4J configuration supported by the Confluent Platform component. + This will either add or update the existing configuration. + items: + type: string + type: array + server: + description: |- + server is a list of server configuration supported by the Confluent Platform component. + This will either add or update existing configuration. + items: + type: string + type: array + type: object + dataVolumeCapacity: + anyOf: + - type: integer + - type: string + description: dataVolumeCapacity specifies the persistent volume capacity + for the Kafka cluster. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + dependencies: + description: dependencies specify the Kafka dependencies, such as + Zookeeper and centralized MDS. + properties: + kRaftController: + description: |- + kRaftController specifies the dependency configuration for the KRaftController cluster. + You cannot configure both zookeeper and kRaftController dependencies. + properties: + clusterRef: + description: clusterRef specifies the name of the KRaft Controller + cluster. + properties: + name: + description: name specifies the name of the Confluent + Platform component cluster. + type: string + namespace: + description: namespace specifies the namespace where the + Confluent Platform component cluster is running. + type: string + required: + - name + type: object + controllerListener: + description: |- + controllerListener specifies the controller listener which must match + the controller listener on the referenced KRaft Controller cluster. + properties: + authentication: + description: authentication specifies the authentication + configuration for the listener. + properties: + jaasConfig: + description: |- + jaasConfig specifies the JaaS configuration. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: |- + jaasConfigPassThrough specifies another way to provide JaaS configuration. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + principalMappingRules: + items: + type: string + type: array + type: + description: |- + type specifies the Kafka or Zookeeper authentication type. + Valid options are `plain`, `digest`, `mtls`, `ldap` & `oauth`. + enum: + - plain + - digest + - mtls + - ldap + - oauth + type: string + required: + - type + type: object + tls: + description: tls specifies the TLS configuration for the + listener. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + type: object + kafkaRest: + description: kafkaRest provides the REST client configuration + for the MDS when RBAC is enabled. + properties: + authentication: + description: authentication specifies the Kafka authentication + for Kafka REST API or MDS. + properties: + bearer: + description: |- + bearer is the authentication mechanism to provide principals. + Only supported in RBAC deployment. + Required when authentication type is set to `bearer`. + This field will be deprecated, please configure oauthbearer instead. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another way + to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provide principals. + Only supported in RBAC deployment. + Required when authentication type is set to `oauthbearer`. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `bearer`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - bearer + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: |- + bootstrapEndpoint specifies Kafka bootstrap endpoint for the admin REST API. It is not needed when RBAC is enabled. + If not configured, then default to the replication listener endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + endpoint: + description: endpoint specifies the custom MDS http|s endpoint. + Not required to configure in most of the scenarios. + minLength: 1 + pattern: ^https?://.* + type: string + tls: + description: tls specifies the client-side TLS configuration + to connect to the Kafka REST API. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + mds: + description: mds specifies the dependency configuration for the + primary MDS. + properties: + endpoint: + description: endpoint defines the primary Kafka cluster boostrap + endpoint. + minLength: 1 + pattern: ^https?://.* + type: string + kafka: + description: kafka specifies the dependency configuration + for Kafka cluster. + properties: + authentication: + description: authentication defines the authentication + for the Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another + way to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap + endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + discovery: + description: discovery specifies the capability to discover + the Kafka cluster. + properties: + name: + description: name is the name of the Confluent Platform + component cluster. + type: string + namespace: + description: |- + namespace is where the Confluent Platform component is running. + The default value is the namespace where CFK is running. + type: string + secretRef: + description: secretRef is the name of the secret used + to discover the Confluent Platform component. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + tls: + description: tls defines the client-side TLS setting for + the Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + tls: + description: tls specifies the TLS configuration for the primary + MDS. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + tokenKeyPair: + description: tokenKeyPair specifies the token key pair for + the MDS. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer defines the directory path in the container + where the MDS token key pair are mounted. + minLength: 1 + type: string + encryptedTokenKey: + description: |- + EncryptedTokenKey boolean value indicating whether the tokenKeypair(private used for signing) is encrypted using a passphrase. If true, cfk + operator will look for a file named mdsTokenKeyPassphrase.txt containing key value pair + mdsTokenKeyPassphrase=. Relevant only for mds server. Ignored if set for a client configuration. + type: boolean + secretRef: + description: secretRef references the name of the secret + that contains the MDS token key pair. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + required: + - endpoint + - kafka + - tokenKeyPair + type: object + schemaRegistry: + description: schemaRegistry specifies the dependency configuration + for the Schema Registry cluster. + properties: + authentication: + description: authentication specifies the authentication for + the Schema Registry cluster. + properties: + basic: + description: basic specifies the configuration for basic + authentication. + properties: + debug: + description: debug enables the basic authentication + debug logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: OAuth specifies the configuration for OAuth + authentication. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass + the basic credential through a directory path in + the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to + pass the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication scheme + for the REST API client. Valid options are `basic` and + `mtls`. + enum: + - basic + - mtls + - oauth + type: string + required: + - type + type: object + tls: + description: tls defines the client-side TLS setting for the + Schema Registry cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + url: + description: url specifies the URL endpoint of the Schema + Registry cluster. + minLength: 1 + pattern: ^https?://.* + type: string + required: + - url + type: object + zookeeper: + description: |- + zookeeper specifies the dependency configuration for Zookeeper. + You cannot configure both zookeeper and kRaftController dependencies. + properties: + authentication: + description: authentication specifies the client side authentication + configuration of Zookeeper for Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another way + to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + discovery: + description: discovery specifies the capability to discover + the Zookeeper cluster. + properties: + name: + description: name is the name of the Confluent Platform + component cluster. + type: string + namespace: + description: |- + namespace is where the Confluent Platform component is running. + The default value is the namespace where CFK is running. + type: string + secretRef: + description: secretRef is the name of the secret used + to discover the Confluent Platform component. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + endpoint: + description: endpoint specifies the Zookeeper endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + tls: + description: tls specifies the TLS configuration of Zookeeper + for Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + type: object + fips: + description: |- + fips specifies the configuration of FIPS compliant Bouncy Castle type Java Keystores for the Kafka cluster's + TLS settings. TLS Secrets must have the keys keystore.bcfks, truststore.bcfks, and jksPassword.txt + properties: + enabled: + description: enabled specifies whether to enable the FIPS configuration + for cp components. + type: boolean + required: + - enabled + type: object + headlessService: + description: headlessService specifies the configuration of the Kubernetes + headless service. + properties: + annotations: + additionalProperties: + type: string + description: |- + annotations is a map of string key and value pairs. + It specifies the annotations to be added to the CFK-created headless service. + These annotations are merged with the injectAnnotations and take precedence. + type: object + x-kubernetes-map-type: granular + labels: + additionalProperties: + type: string + description: |- + labels is a map of string key and value pairs. + It specifies the labels to be added to the CFK-created headless service. + These labels are merged with the injectLabels and take precedence. + type: object + x-kubernetes-map-type: granular + publishNotReadyAddresses: + description: |- + publishNotReadyAddresses specifies the publishNotReadyAddresses field. + For Kafka, this value must be true. The default value is true. + type: boolean + type: object + identityProvider: + description: |- + identityProvider specifies the identity provider configuration. + It is only required for the Kafka authentication type `ldap`. + When the MDS is enabled, this property is ignored, and + the LDAP configuration in `spec.services.mds.provider` will be used. + properties: + ldap: + description: ldap defines the LDAP service configuration. + properties: + address: + description: address defines the LDAP server address. + type: string + authentication: + description: LdapAuthentication specifies the LDAP authentication + configuration. + properties: + simple: + description: simple specifies simple authentication configuration + for the LDAP. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer defines the directory path in the container + where the credentials are mounted. + minLength: 1 + type: string + secretRef: + description: secretRef references the name of the + secret that contains the credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: type defines the authentication method for + LDAP. Valid options are `simple` and `mtls`. + enum: + - simple + - mtls + type: string + required: + - type + type: object + configurations: + description: configurations defines the LDAP configurations + for Confluent RBAC. + properties: + groupMemberAttribute: + description: groupMemberAttribute specifies the LDAP group + member attribute. + minLength: 1 + type: string + groupMemberAttributePattern: + description: groupMemberAttributePattern specifies the + regular expression pattern for the LDAP group member + attribute. + minLength: 1 + type: string + groupNameAttribute: + description: groupNameAttribute specifies the LDAP group + name attribute. + minLength: 1 + type: string + groupObjectClass: + description: groupObjectClass specifies the LDAP group + object class. + minLength: 1 + type: string + groupSearchBase: + description: groupSearchBase specifies the LDAP search + base for the group-based search. + minLength: 1 + type: string + groupSearchFilter: + description: groupSearchFilter specifies the LDAP search + filter for the group-based search. + minLength: 1 + type: string + groupSearchScope: + description: groupSearchScope specifies the LDAP search + scope for the group-based search. + format: int32 + type: integer + userMemberOfAttributePattern: + description: userMemberOfAttributePattern specifies the + regular expression pattern for the LDAP user member + attribute. + minLength: 1 + type: string + userNameAttribute: + description: userNameAttribute specifies the LDAP username + attribute. + minLength: 1 + type: string + userObjectClass: + description: userObjectClass specifies the LDAP user object + class. + minLength: 1 + type: string + userSearchBase: + description: userSearchBase specifies the LDAP search + base for the user-based search. + minLength: 1 + type: string + userSearchFilter: + description: userSearchFilter specifies the LDAP search + filter for the user-based search. + minLength: 1 + type: string + userSearchScope: + description: userSearchScope specifies the LDAP search + scope for the user-based search. + format: int32 + type: integer + type: object + tls: + description: tls specifies the TLS configuration for the LDAP. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + required: + - address + - authentication + - configurations + type: object + oauth: + description: oauth defines the OAuth service configuration. + properties: + configurations: + description: configurations defines the OAuth configurations. + properties: + audience: + description: audience specifies the audience claim in + the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected issuer + in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of claim + in token for identifying the groups of subject in the + JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout with + IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry backoff + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff with + IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim in JWT + to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + tls: + description: tls specifies the TLS configuration for the OAuth + IDP. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + required: + - configurations + type: object + oidc: + description: |- + this field has been superseded with sso field + oidc defines the OIDC service configuration. + properties: + clientCredentials: + description: clientCredentials define the IDP clientID and + clientSecret. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer defines the directory path in the container + where the credentials are mounted. + minLength: 1 + type: string + secretRef: + description: secretRef references the name of the secret + that contains the credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + configurations: + description: configurations defines the OIDC configurations. + properties: + audience: + description: audience specifies the audience claim in + the JWT payload. + minLength: 1 + type: string + authorizeBaseEndpointUri: + description: authorizeBaseEndpointUri specifies the base + uri for authorize endpoint. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of claim + in token for identifying the groups of subject in the + JWT payload. + minLength: 1 + type: string + groupsClaimScope: + description: |- + groupsClaimScope specifies additional scope needed for the token to contain groups claim (field). + Leave this field empty (or null) if id token always contains the claims identified as groups. + minLength: 1 + type: string + issuer: + description: |- + issuer specifies the authorization server's URL. + This value should match the issuer claim ("iss") in id tokens issued by Authorization Server? + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + refreshToken: + description: refreshToken specifies whether offline_access + scope should be requested in the authorization URI. + type: boolean + sessionMaxTimeout: + description: sessionMaxTimeout specifies the maximum expiration + time for a user's session. + format: int32 + type: integer + sessionTokenExpiry: + description: sessionTokenExpiry specifies the validity + of cookie issued by Confluent. + format: int32 + type: integer + subClaimName: + description: subClaimName specifies name of claim in JWT + to use for the subject. + minLength: 1 + type: string + tokenBaseEndpointUri: + description: tokenBaseEndpointUri specifies the base uri + for token endpoint. + minLength: 1 + type: string + required: + - authorizeBaseEndpointUri + - issuer + - jwksEndpointUri + - refreshToken + - tokenBaseEndpointUri + type: object + enabled: + description: enabled specifies whether the SSO is enabled. + type: boolean + required: + - clientCredentials + - configurations + - enabled + type: object + sso: + description: sso defines the SSO service configuration. + properties: + clientCredentials: + description: clientCredentials define the IDP clientID and + clientSecret. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer defines the directory path in the container + where the credentials are mounted. + minLength: 1 + type: string + secretRef: + description: secretRef references the name of the secret + that contains the credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + configurations: + description: configurations defines the OIDC configurations. + properties: + audience: + description: audience specifies the audience claim in + the JWT payload. + minLength: 1 + type: string + authorizeBaseEndpointUri: + description: authorizeBaseEndpointUri specifies the base + uri for authorize endpoint. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of claim + in token for identifying the groups of subject in the + JWT payload. + minLength: 1 + type: string + groupsClaimScope: + description: |- + groupsClaimScope specifies additional scope needed for the token to contain groups claim (field). + Leave this field empty (or null) if id token always contains the claims identified as groups. + minLength: 1 + type: string + issuer: + description: |- + issuer specifies the authorization server's URL. + This value should match the issuer claim ("iss") in id tokens issued by Authorization Server? + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + refreshToken: + description: refreshToken specifies whether offline_access + scope should be requested in the authorization URI. + type: boolean + sessionMaxTimeout: + description: sessionMaxTimeout specifies the maximum expiration + time for a user's session. + format: int32 + type: integer + sessionTokenExpiry: + description: sessionTokenExpiry specifies the validity + of cookie issued by Confluent. + format: int32 + type: integer + subClaimName: + description: subClaimName specifies name of claim in JWT + to use for the subject. + minLength: 1 + type: string + tokenBaseEndpointUri: + description: tokenBaseEndpointUri specifies the base uri + for token endpoint. + minLength: 1 + type: string + required: + - authorizeBaseEndpointUri + - issuer + - jwksEndpointUri + - refreshToken + - tokenBaseEndpointUri + type: object + enabled: + description: enabled specifies whether the SSO is enabled. + type: boolean + required: + - clientCredentials + - configurations + - enabled + type: object + type: + description: This field has been deprecated and its value will + be ignored if set. + type: string + type: object + image: + description: |- + image specifies the application and the init docker image configurations. + A change to this setting will roll the cluster. + properties: + application: + description: |- + application is the Docker image name of the application. Specify + `//:`. + pattern: .+:.+ + type: string + init: + description: |- + init is the init-container name. Specify + `//:`. + pattern: .+:.+ + type: string + pullPolicy: + description: |- + pullPolicy is the policy for pulling images. Valid options are `Always`, `Never`, and `IfNotPresent`. + The default value is `IfNotPresent`. + enum: + - Always + - Never + - IfNotPresent + type: string + pullSecretRef: + description: |- + pullSecretRef references the secrets in the same namespace to be used for pulling images. + Image pull secrets are distinct from secrets because secrets + can be mounted in the pod, but image pull secrets are only accessed by `kubelet`. + More info: https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod + items: + type: string + type: array + required: + - application + - init + type: object + injectAnnotations: + additionalProperties: + type: string + description: |- + injectAnnotations are the annotations injected to the internal resources that CFK created. + The internal annotations are preserved and cannot be overridden. + For pod annotations, use `podTemplate.annotations`. + type: object + x-kubernetes-map-type: granular + injectLabels: + additionalProperties: + type: string + description: |- + injectLabels are the labels injected to the internal resources that CFK created. + The internal labels are preserved and cannot be overridden. + For pod labels, use `podTemplate.labels`. + type: object + x-kubernetes-map-type: granular + k8sClusterDomain: + description: |- + k8sClusterDomain specifies the configuration of the Kubernetes cluster domain. + The default is the `cluster.local` domain. + type: string + license: + description: license specifies the license configuration for the Confluent + Platform component. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + the license key is mounted. More info: + https://docs.confluent.io/operator/current/co-license.html#update-component-level-licenses + minLength: 1 + type: string + globalLicense: + description: globalLicense specifies whether the Confluent Platform + component shares the common global license. + type: boolean + secretRef: + description: |- + secretRef references the secret that provides the license for the Confluent Platform component. + More info: https://docs.confluent.io/operator/current/co-license.html#update-component-level-licenses + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + listeners: + description: listeners specify the listeners configurations. + properties: + custom: + description: custom defines the list of KafkaCustomListener. + items: + description: KafkaCustomListener defines the Kafka custom listener. + properties: + authentication: + description: authentication specifies the authentication + configuration for the listener. + properties: + jaasConfig: + description: |- + jaasConfig specifies the JaaS configuration. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: |- + jaasConfigPassThrough specifies another way to provide JaaS configuration. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups of + subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + principalMappingRules: + items: + type: string + type: array + type: + description: |- + type specifies the Kafka or Zookeeper authentication type. + Valid options are `plain`, `digest`, `mtls`, `ldap` & `oauth`. + enum: + - plain + - digest + - mtls + - ldap + - oauth + type: string + required: + - type + type: object + externalAccess: + description: externalAccess defines the external access + configuration for the Kafka cluster. + properties: + loadBalancer: + description: loadBalancer specifies the configuration + to create Kubernetes load balancer services. + properties: + advertisedPort: + description: |- + advertisedPort specifies the advertised port for Kafka external access. + If not configured, it will be the same as the listener port. + Information about the advertised port can be retrieved through the status API. + format: int32 + type: integer + annotations: + additionalProperties: + type: string + description: annotations is a map of string key + and value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + bootstrapPrefix: + description: |- + bootstrapPrefix specifies the prefix for the Kafka bootstrap advertised endpoint and will be added as `bootstrapPrefix.domain`. + The default value is the Kafka cluster name. + minLength: 1 + type: string + brokerPrefix: + description: |- + brokerPrefix specifies the prefix for the Kafka broker advertised endpoint and will be added as `brokerPrefix.domain`. + The default value is `b`, such as `b#.domain` where `#` starts from `0` to the replicas count. + minLength: 1 + type: string + domain: + description: domain is the domain name of the component + cluster. + minLength: 1 + type: string + externalTrafficPolicy: + description: externalTrafficPolicy specifies the + external traffic policy for the service. Valid + options are `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this + service. + type: object + x-kubernetes-map-type: granular + loadBalancerSourceRanges: + description: loadBalancerSourceRanges specify the + source ranges. + items: + type: string + type: array + servicePorts: + description: servicePorts specify the user-provided + service port(s). + items: + description: ServicePort contains information + on service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed + by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the + configurations of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - domain + type: object + nodePort: + description: nodePort specifies the configuration to + create Kubernetes node port services. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to `://:, where`podId` starts from `0` to `replicaCount - 1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key + and value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + externalTrafficPolicy: + description: |- + externalTrafficPolicy specifies the external traffic policy for the service. + Valid options are `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + host: + description: host defines the host name of the cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this + service. + type: object + x-kubernetes-map-type: granular + nodePortOffset: + description: |- + nodePortOffset specifies the starting offset of the node ports. The port numbers go in ascending order with respect + to the replicas count. + NodePort service creation fails if the node port is not in the range supported by the Kubernetes API server. + The default Kubernetes Node Port range is `30000` - `32762`. + format: int32 + minimum: 0 + type: integer + servicePorts: + description: |- + servicePorts specify user-provided service port(s). + For Kafka with the nodePort type, this setting is only applied to Kafka bootstrap service. + items: + description: ServicePort contains information + on service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed + by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the + configurations of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - host + - nodePortOffset + type: object + route: + description: route specifies the configuration to create + route services in OpenShift. + properties: + annotations: + additionalProperties: + type: string + description: annotations is a map of string key + and value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + bootstrapPrefix: + description: |- + bootstrapPrefix specifies the prefix for the Kafka bootstrap advertised endpoint and will be added as `bootstrapPrefix.domain`. + The default value is the Kafka cluster name. + minLength: 1 + type: string + brokerPrefix: + description: |- + brokerPrefix specifies the prefix for the Kafka broker advertised endpoint and will be added as `brokerPrefix.domain`. + The default value is `b`, such as `b#.domain` where `#` starts from `0` to the replicas count. + minLength: 1 + type: string + domain: + description: domain specifies the domain name of + the Confluent component cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this + service. + type: object + x-kubernetes-map-type: granular + wildcardPolicy: + description: |- + wildcardPolicy allows you to define a route that covers all hosts within a domain. Valid options are `Subdomain` and `None`. + The default value is `None`. + enum: + - Subdomain + - None + type: string + required: + - domain + type: object + staticForHostBasedRouting: + description: |- + staticForHostBasedRouting enables external access by doing host based + routing through the SNI capability. + With this schema, CFK only configures Kafka advertised listeners, and no Kubernetes external + service is created. + properties: + brokerPrefix: + description: |- + brokerPrefix specifies the prefix for the broker advertised endpoints and are added as `brokerPrefix.domain`. + If not configured, it will add `b` as a prefix, such as `b#.domain` where `#` will start from `0` to the replicas count. + minLength: 1 + type: string + domain: + description: domain specifies the domain name for + the Kafka cluster. + minLength: 1 + type: string + port: + description: port specifies the port to be used + in the advertised listener for a broker. + format: int32 + type: integer + required: + - domain + - port + type: object + staticForPortBasedRouting: + description: |- + staticForPortBasedRouting enables external access by port routing. + With this schema, CFK only configures Kafka advertised listeners, and no Kubernetes external + service is created. + properties: + host: + description: host defines the host name to be used + in the advertised listener for a broker. + minLength: 1 + type: string + portOffset: + description: |- + portOffset specifies the starting port number. The port numbers go in ascending order with + respect to the replicas count. + format: int32 + type: integer + required: + - host + - portOffset + type: object + type: + description: |- + type specifies the Kubernetes service for external access. + Valid options are `loadBalancer`, `nodePort`, `route`, `staticForPortBasedRouting`, and `staticForHostBasedRouting`. + enum: + - loadBalancer + - nodePort + - route + - staticForPortBasedRouting + - staticForHostBasedRouting + type: string + required: + - type + type: object + name: + description: |- + name specifies the name of the custom listener. + `internal`, `external`, and `token` are reserved by CFK and + can't be used for this property. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + port binds the given port to the custom listener. Port numbers lower than `9093` are + reserved by CFK. + format: int32 + minimum: 9093 + type: integer + tls: + description: tls specifies the TLS configuration for the + listener. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + required: + - name + - port + type: object + type: array + external: + description: external specifies the Kafka external listener. + properties: + authentication: + description: authentication specifies the authentication configuration + for the listener. + properties: + jaasConfig: + description: |- + jaasConfig specifies the JaaS configuration. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: |- + jaasConfigPassThrough specifies another way to provide JaaS configuration. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + principalMappingRules: + items: + type: string + type: array + type: + description: |- + type specifies the Kafka or Zookeeper authentication type. + Valid options are `plain`, `digest`, `mtls`, `ldap` & `oauth`. + enum: + - plain + - digest + - mtls + - ldap + - oauth + type: string + required: + - type + type: object + externalAccess: + description: externalAccess defines the external access configuration + for the Kafka cluster. + properties: + loadBalancer: + description: loadBalancer specifies the configuration + to create Kubernetes load balancer services. + properties: + advertisedPort: + description: |- + advertisedPort specifies the advertised port for Kafka external access. + If not configured, it will be the same as the listener port. + Information about the advertised port can be retrieved through the status API. + format: int32 + type: integer + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + bootstrapPrefix: + description: |- + bootstrapPrefix specifies the prefix for the Kafka bootstrap advertised endpoint and will be added as `bootstrapPrefix.domain`. + The default value is the Kafka cluster name. + minLength: 1 + type: string + brokerPrefix: + description: |- + brokerPrefix specifies the prefix for the Kafka broker advertised endpoint and will be added as `brokerPrefix.domain`. + The default value is `b`, such as `b#.domain` where `#` starts from `0` to the replicas count. + minLength: 1 + type: string + domain: + description: domain is the domain name of the component + cluster. + minLength: 1 + type: string + externalTrafficPolicy: + description: externalTrafficPolicy specifies the external + traffic policy for the service. Valid options are + `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + loadBalancerSourceRanges: + description: loadBalancerSourceRanges specify the + source ranges. + items: + type: string + type: array + servicePorts: + description: servicePorts specify the user-provided + service port(s). + items: + description: ServicePort contains information on + service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by + this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - domain + type: object + nodePort: + description: nodePort specifies the configuration to create + Kubernetes node port services. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to `://:, where`podId` starts from `0` to `replicaCount - 1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + externalTrafficPolicy: + description: |- + externalTrafficPolicy specifies the external traffic policy for the service. + Valid options are `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + host: + description: host defines the host name of the cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + nodePortOffset: + description: |- + nodePortOffset specifies the starting offset of the node ports. The port numbers go in ascending order with respect + to the replicas count. + NodePort service creation fails if the node port is not in the range supported by the Kubernetes API server. + The default Kubernetes Node Port range is `30000` - `32762`. + format: int32 + minimum: 0 + type: integer + servicePorts: + description: |- + servicePorts specify user-provided service port(s). + For Kafka with the nodePort type, this setting is only applied to Kafka bootstrap service. + items: + description: ServicePort contains information on + service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by + this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - host + - nodePortOffset + type: object + route: + description: route specifies the configuration to create + route services in OpenShift. + properties: + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + bootstrapPrefix: + description: |- + bootstrapPrefix specifies the prefix for the Kafka bootstrap advertised endpoint and will be added as `bootstrapPrefix.domain`. + The default value is the Kafka cluster name. + minLength: 1 + type: string + brokerPrefix: + description: |- + brokerPrefix specifies the prefix for the Kafka broker advertised endpoint and will be added as `brokerPrefix.domain`. + The default value is `b`, such as `b#.domain` where `#` starts from `0` to the replicas count. + minLength: 1 + type: string + domain: + description: domain specifies the domain name of the + Confluent component cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + wildcardPolicy: + description: |- + wildcardPolicy allows you to define a route that covers all hosts within a domain. Valid options are `Subdomain` and `None`. + The default value is `None`. + enum: + - Subdomain + - None + type: string + required: + - domain + type: object + staticForHostBasedRouting: + description: |- + staticForHostBasedRouting enables external access by doing host based + routing through the SNI capability. + With this schema, CFK only configures Kafka advertised listeners, and no Kubernetes external + service is created. + properties: + brokerPrefix: + description: |- + brokerPrefix specifies the prefix for the broker advertised endpoints and are added as `brokerPrefix.domain`. + If not configured, it will add `b` as a prefix, such as `b#.domain` where `#` will start from `0` to the replicas count. + minLength: 1 + type: string + domain: + description: domain specifies the domain name for + the Kafka cluster. + minLength: 1 + type: string + port: + description: port specifies the port to be used in + the advertised listener for a broker. + format: int32 + type: integer + required: + - domain + - port + type: object + staticForPortBasedRouting: + description: |- + staticForPortBasedRouting enables external access by port routing. + With this schema, CFK only configures Kafka advertised listeners, and no Kubernetes external + service is created. + properties: + host: + description: host defines the host name to be used + in the advertised listener for a broker. + minLength: 1 + type: string + portOffset: + description: |- + portOffset specifies the starting port number. The port numbers go in ascending order with + respect to the replicas count. + format: int32 + type: integer + required: + - host + - portOffset + type: object + type: + description: |- + type specifies the Kubernetes service for external access. + Valid options are `loadBalancer`, `nodePort`, `route`, `staticForPortBasedRouting`, and `staticForHostBasedRouting`. + enum: + - loadBalancer + - nodePort + - route + - staticForPortBasedRouting + - staticForHostBasedRouting + type: string + required: + - type + type: object + tls: + description: tls specifies the TLS configuration for the listener. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + internal: + description: internal specifies the internal listener. + properties: + authentication: + description: authentication specifies the authentication configuration + for the listener. + properties: + jaasConfig: + description: |- + jaasConfig specifies the JaaS configuration. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: |- + jaasConfigPassThrough specifies another way to provide JaaS configuration. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + principalMappingRules: + items: + type: string + type: array + type: + description: |- + type specifies the Kafka or Zookeeper authentication type. + Valid options are `plain`, `digest`, `mtls`, `ldap` & `oauth`. + enum: + - plain + - digest + - mtls + - ldap + - oauth + type: string + required: + - type + type: object + tls: + description: tls specifies the TLS configuration for the listener. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + replication: + description: replication specifies the Kafka replication listener. + properties: + authentication: + description: authentication specifies the authentication configuration + for the listener. + properties: + jaasConfig: + description: |- + jaasConfig specifies the JaaS configuration. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: |- + jaasConfigPassThrough specifies another way to provide JaaS configuration. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + principalMappingRules: + items: + type: string + type: array + type: + description: |- + type specifies the Kafka or Zookeeper authentication type. + Valid options are `plain`, `digest`, `mtls`, `ldap` & `oauth`. + enum: + - plain + - digest + - mtls + - ldap + - oauth + type: string + required: + - type + type: object + externalAccess: + description: externalAccess defines the external access configuration + for the Kafka cluster. + properties: + loadBalancer: + description: loadBalancer specifies the configuration + to create Kubernetes load balancer services. + properties: + advertisedPort: + description: |- + advertisedPort specifies the advertised port for Kafka external access. + If not configured, it will be the same as the listener port. + Information about the advertised port can be retrieved through the status API. + format: int32 + type: integer + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + bootstrapPrefix: + description: |- + bootstrapPrefix specifies the prefix for the Kafka bootstrap advertised endpoint and will be added as `bootstrapPrefix.domain`. + The default value is the Kafka cluster name. + minLength: 1 + type: string + brokerPrefix: + description: |- + brokerPrefix specifies the prefix for the Kafka broker advertised endpoint and will be added as `brokerPrefix.domain`. + The default value is `b`, such as `b#.domain` where `#` starts from `0` to the replicas count. + minLength: 1 + type: string + domain: + description: domain is the domain name of the component + cluster. + minLength: 1 + type: string + externalTrafficPolicy: + description: externalTrafficPolicy specifies the external + traffic policy for the service. Valid options are + `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + loadBalancerSourceRanges: + description: loadBalancerSourceRanges specify the + source ranges. + items: + type: string + type: array + servicePorts: + description: servicePorts specify the user-provided + service port(s). + items: + description: ServicePort contains information on + service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by + this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - domain + type: object + nodePort: + description: nodePort specifies the configuration to create + Kubernetes node port services. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to `://:, where`podId` starts from `0` to `replicaCount - 1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + externalTrafficPolicy: + description: |- + externalTrafficPolicy specifies the external traffic policy for the service. + Valid options are `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + host: + description: host defines the host name of the cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + nodePortOffset: + description: |- + nodePortOffset specifies the starting offset of the node ports. The port numbers go in ascending order with respect + to the replicas count. + NodePort service creation fails if the node port is not in the range supported by the Kubernetes API server. + The default Kubernetes Node Port range is `30000` - `32762`. + format: int32 + minimum: 0 + type: integer + servicePorts: + description: |- + servicePorts specify user-provided service port(s). + For Kafka with the nodePort type, this setting is only applied to Kafka bootstrap service. + items: + description: ServicePort contains information on + service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by + this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - host + - nodePortOffset + type: object + route: + description: route specifies the configuration to create + route services in OpenShift. + properties: + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + bootstrapPrefix: + description: |- + bootstrapPrefix specifies the prefix for the Kafka bootstrap advertised endpoint and will be added as `bootstrapPrefix.domain`. + The default value is the Kafka cluster name. + minLength: 1 + type: string + brokerPrefix: + description: |- + brokerPrefix specifies the prefix for the Kafka broker advertised endpoint and will be added as `brokerPrefix.domain`. + The default value is `b`, such as `b#.domain` where `#` starts from `0` to the replicas count. + minLength: 1 + type: string + domain: + description: domain specifies the domain name of the + Confluent component cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + wildcardPolicy: + description: |- + wildcardPolicy allows you to define a route that covers all hosts within a domain. Valid options are `Subdomain` and `None`. + The default value is `None`. + enum: + - Subdomain + - None + type: string + required: + - domain + type: object + staticForHostBasedRouting: + description: |- + staticForHostBasedRouting enables external access by doing host based + routing through the SNI capability. + With this schema, CFK only configures Kafka advertised listeners, and no Kubernetes external + service is created. + properties: + brokerPrefix: + description: |- + brokerPrefix specifies the prefix for the broker advertised endpoints and are added as `brokerPrefix.domain`. + If not configured, it will add `b` as a prefix, such as `b#.domain` where `#` will start from `0` to the replicas count. + minLength: 1 + type: string + domain: + description: domain specifies the domain name for + the Kafka cluster. + minLength: 1 + type: string + port: + description: port specifies the port to be used in + the advertised listener for a broker. + format: int32 + type: integer + required: + - domain + - port + type: object + staticForPortBasedRouting: + description: |- + staticForPortBasedRouting enables external access by port routing. + With this schema, CFK only configures Kafka advertised listeners, and no Kubernetes external + service is created. + properties: + host: + description: host defines the host name to be used + in the advertised listener for a broker. + minLength: 1 + type: string + portOffset: + description: |- + portOffset specifies the starting port number. The port numbers go in ascending order with + respect to the replicas count. + format: int32 + type: integer + required: + - host + - portOffset + type: object + type: + description: |- + type specifies the Kubernetes service for external access. + Valid options are `loadBalancer`, `nodePort`, `route`, `staticForPortBasedRouting`, and `staticForHostBasedRouting`. + enum: + - loadBalancer + - nodePort + - route + - staticForPortBasedRouting + - staticForHostBasedRouting + type: string + required: + - type + type: object + tls: + description: tls specifies the TLS configuration for the listener. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + type: object + metricReporter: + description: |- + metricsReporter specifies the configuration of the metric reporter. The metric reporter is enabled by default. + If authentication and TLS are not set, the metrics reporter uses internal listener's authentication and TLS . + properties: + authentication: + description: authentication specifies the Kafka client-side authentication + configuration. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side JaaS + configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another way to + provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim in + the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected issuer + in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of claim + in token for identifying the groups of subject in the + JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout with + IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry backoff + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff with + IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim in JWT + to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap endpoint. + type: string + enabled: + description: enabled specifies whether to enable or disable the + metric reporter. + type: boolean + replicationFactor: + description: replicationFactor specifies the number of replicas + in the metric topic. + format: int32 + type: integer + tls: + description: tls specifies the Kafka client-side TLS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + required: + - enabled + type: object + metrics: + description: metrics specify the security settings for the metric + services. + properties: + authentication: + description: authentication specifies the authentication configuration + for the metrics. + properties: + type: + description: type specifies the metrics authentication method. + The valid option is `mtls`. + enum: + - mtls + type: string + required: + - type + type: object + prometheus: + description: prometheus specifies the configuration overrides + for the JMX-Prometheus exporter. + properties: + blacklist: + items: + type: string + type: array + rules: + items: + description: Rule defines the Prometheus Exporter rule override. + properties: + attrNameSnakeCase: + type: boolean + cache: + type: boolean + help: + minLength: 1 + type: string + labels: + additionalProperties: + type: string + type: object + x-kubernetes-map-type: granular + name: + minLength: 1 + type: string + pattern: + minLength: 1 + type: string + type: + minLength: 1 + type: string + value: + minLength: 1 + type: string + valueFactor: + anyOf: + - type: integer + - type: string + default: 1 + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: array + whitelist: + items: + type: string + type: array + type: object + tls: + description: tls specifies the TLS configuration for the metrics. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + mountedSecrets: + description: |- + mountedSecrets list the secrets injected to + the underlying statefulset configuration. The secret reference is mounted + in the default path `/mnt/secrets/`. The underlying resources + will follow the secret as a file configuration. + More info: https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets-as-files-from-a-pod. + A change to this setting will roll the cluster. + items: + description: |- + MountedSecrets provides a way to inject a custom secret to the underlying + statefulset. + properties: + keyItems: + description: keyItems are key and path names. + items: + description: Maps a string key to a path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + secretRef: + description: secretRef references the name of the secret. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + type: array + mountedVolumes: + description: |- + mountedVolumes list the custom volumes that need to be mounted into the + underlying statefulset. + A change to this setting will roll the cluster. + properties: + volumeMounts: + description: |- + volumeMounts specify the list of volume mounts for the pods in the + statefulset. + items: + description: VolumeMount describes a mounting of a Volume within + a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + description: |- + volumes specify the list of volumes that can be mounted into the pods + of statefulset. + items: + description: Volume represents a named volume in a pod that + may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + format: int32 + type: integer + readOnly: + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: boolean + volumeID: + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + required: + - volumeID + type: object + azureDisk: + description: azureDisk represents an Azure Data Disk mount + on the host and bind mount to the pod. + properties: + cachingMode: + description: 'cachingMode is the Host Caching mode: + None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the data disk in + the blob storage + type: string + diskURI: + description: diskURI is the URI of data disk in the + blob storage + type: string + fsType: + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: multiple + blob disks per storage account Dedicated: single + blob disk per storage account Managed: azure managed + data disk (only in managed availability set). defaults + to shared' + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: azureFile represents an Azure File Service + mount on the host and bind mount to the pod. + properties: + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of secret that + contains Azure Storage Account Name and Key + type: string + shareName: + description: shareName is the azure share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: cephFS represents a Ceph FS mount on the host + that shares a pod's lifetime + properties: + monitors: + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + items: + type: string + type: array + path: + description: 'path is Optional: Used as the mounted + root, rather than the full Ceph tree, default is /' + type: string + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: boolean + secretFile: + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + secretRef: + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + required: + - monitors + type: object + cinder: + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: boolean + secretRef: + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + description: csi (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers (Beta feature). + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward API about the + pod that should populate this volume + properties: + defaultMode: + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents information + to create the file containing the pod field + properties: + fieldRef: + description: 'Required: Selects a field of the + pod: only annotations, labels, name and namespace + are supported.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must not + be absolute or contain the ''..'' path. Must + be utf-8 encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + emptyDir: + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + properties: + medium: + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: 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]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. + properties: + volumeClaimTemplate: + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + + Required, must not be nil. + properties: + metadata: + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. + type: object + spec: + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim + are also valid here. + properties: + accessModes: + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + items: + type: string + type: array + dataSource: + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + 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. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any 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. + 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 + required: + - kind + - name + type: object + resources: + description: |- + resources represents the minimum resources the volume should have. + If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + selector: + description: selector is a label query over + volumes to consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + type: string + volumeAttributesClassName: + description: |- + volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. + If specified, the CSI driver will create or update the volume with the attributes defined + in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, + it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass + will be applied to the claim but it's not allowed to reset this field to empty string once it is set. + If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass + will be set by the persistentvolume controller if it exists. + If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be + set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource + exists. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#volumeattributesclass + (Alpha) Using this field requires the VolumeAttributesClass feature gate to be enabled. + type: string + volumeMode: + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding reference + to the PersistentVolume backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource that + is attached to a kubelet's host machine and then exposed + to the pod. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + lun: + description: 'lun is Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC target worldwide + names (WWNs)' + items: + type: string + type: array + wwids: + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. + items: + type: string + type: array + type: object + flexVolume: + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. + properties: + driver: + description: driver is the name of the driver to use + for this volume. + type: string + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this field holds + extra command options if any.' + type: object + readOnly: + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + description: flocker represents a Flocker volume attached + to a kubelet's host machine. This depends on the Flocker + control service being running + properties: + datasetName: + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of the dataset. + This is unique identifier of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + properties: + fsType: + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + format: int32 + type: integer + pdName: + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: boolean + required: + - pdName + type: object + gitRepo: + description: |- + gitRepo represents a git repository at a particular revision. + DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. + properties: + directory: + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash for the specified + revision. + type: string + required: + - repository + type: object + glusterfs: + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/glusterfs/README.md + properties: + endpoints: + description: |- + endpoints is the endpoint name that details Glusterfs topology. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + path: + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + readOnly: + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- + TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not + mount host directories as read/write. + properties: + path: + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + type: + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + required: + - path + type: object + iscsi: + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://examples.k8s.io/volumes/iscsi/README.md + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether support + iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether support + iSCSI Session CHAP authentication + type: boolean + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + initiatorName: + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified Name. + type: string + iscsiInterface: + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret for iSCSI + target and initiator authentication + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + nfs: + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + properties: + path: + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + readOnly: + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: boolean + server: + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + properties: + claimName: + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + type: string + readOnly: + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: photonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets host + machine + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies Photon Controller + persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: portworxVolume represents a portworx volume + attached and mounted on kubelets host machine + properties: + fsType: + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one resources secrets, + configmaps, and downward API + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + sources: + description: sources is the list of volume projections + items: + description: Projection that may be projected along + with other supported volume types + properties: + clusterTrustBundle: + description: |- + ClusterTrustBundle allows a pod to access the `.spec.trustBundle` field + of ClusterTrustBundle objects in an auto-updating file. + + + Alpha, gated by the ClusterTrustBundleProjection feature gate. + + + ClusterTrustBundle objects can either be selected by name, or by the + combination of signer name and a label selector. + + + Kubelet performs aggressive normalization of the PEM contents written + into the pod filesystem. Esoteric PEM features such as inter-block + comments and block headers are stripped. Certificates are deduplicated. + The ordering of certificates within the file is arbitrary, and Kubelet + may change the order over time. + properties: + labelSelector: + description: |- + Select all ClusterTrustBundles that match this label selector. Only has + effect if signerName is set. Mutually-exclusive with name. If unset, + interpreted as "match nothing". If set but empty, interpreted as "match + everything". + 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 + name: + description: |- + Select a single ClusterTrustBundle by object name. Mutually-exclusive + with signerName and labelSelector. + type: string + optional: + description: |- + If true, don't block pod startup if the referenced ClusterTrustBundle(s) + aren't available. If using name, then the named ClusterTrustBundle is + allowed not to exist. If using signerName, then the combination of + signerName and labelSelector is allowed to match zero + ClusterTrustBundles. + type: boolean + path: + description: Relative path from the volume + root to write the bundle. + type: string + signerName: + description: |- + Select all ClusterTrustBundles that match this signer name. + Mutually-exclusive with name. The contents of all selected + ClusterTrustBundles will be unified and deduplicated. + type: string + required: + - path + type: object + configMap: + description: configMap information about the configMap + data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional specify whether the + ConfigMap or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about the + downwardAPI data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a field + of the pod: only annotations, labels, + name and namespace are supported.' + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in terms + of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to + select in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file to + be created. Must not be absolute or + contain the ''..'' path. Must be utf-8 + encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env + vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + secret: + description: secret information about the secret + data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional field specify whether + the Secret or its key must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information + about the serviceAccountToken data to project + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + format: int64 + type: integer + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + description: quobyte represents a Quobyte mount on the host + that shares a pod's lifetime + properties: + group: + description: |- + group to map volume access to + Default is no group + type: string + readOnly: + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes + type: string + tenant: + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: |- + user to map volume access to + Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references an already + created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/rbd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + image: + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + keyring: + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + monitors: + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + items: + type: string + type: array + pool: + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: boolean + secretRef: + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + required: + - image + - monitors + type: object + scaleIO: + description: scaleIO represents a ScaleIO persistent volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". + type: string + gateway: + description: gateway is the host address of the ScaleIO + API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name of the ScaleIO + Protection Domain for the configured storage. + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + description: sslEnabled Flag enable/disable SSL communication + with Gateway, default false + type: boolean + storageMode: + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO Storage Pool + associated with the protection domain. + type: string + system: + description: system is the name of the storage system + as configured in ScaleIO. + type: string + volumeName: + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: optional field specify whether the Secret + or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + type: object + storageos: + description: storageOS represents a StorageOS volume attached + and mounted on Kubernetes nodes. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. + type: string + volumeNamespace: + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: vsphereVolume represents a vSphere volume attached + and mounted on kubelets host machine + properties: + fsType: + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage Policy Based + Management (SPBM) profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage Policy + Based Management (SPBM) profile name. + type: string + volumePath: + description: volumePath is the path that identifies + vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + required: + - volumeMounts + - volumes + type: object + oneReplicaPerNode: + description: |- + oneReplicaPerNode controls whether to run 1 pod per node using the pod anti-affinity capability. + Enabling this configuration in an existing cluster will roll the cluster. + type: boolean + passwordEncoder: + description: passwordEncoder specifies password encoder secret for + Kafka. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer contains the directory path in the container where + the required secret is mounted. + Directory should have the file `password-encoder.txt`. The contents should include a new password. + Old password is optional and required only for rotation. + More info: https://docs.confluent.io/operator/current/co-password-encoder-secret. + type: string + secretRef: + description: |- + secretRef specifies the secret name. The secret should have the key + `password-encoder.txt`. The contents should include a new password. + Old password is optional and required only for rotation. + More info: https://docs.confluent.io/operator/current/co-password-encoder-secret. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + pdb: + description: |- + configures PodDisruptionBudget for the Confluent Platform component. + by default PDB is configured based on pre-detemined formula. + properties: + enabled: + description: enabled specifies whether the PodDisruptionBudget + is enabled + type: boolean + maxUnavailable: + description: maxUnavailable is the maximum number of pods that + can be unavailable during the disruption. + format: int32 + type: integer + required: + - enabled + type: object + podTemplate: + description: podTemplate specifies the statefulset pod template configuration. + properties: + affinity: + description: |- + affinity specifies a group of affinity scheduling rules. + More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + type: array + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + annotations: + additionalProperties: + type: string + description: |- + annotations is a map of string key and value pairs stored with the resource and + may be set by external tools to store and retrieve arbitrary metadata. They + are not queryable and should be preserved when modifying objects. More + info: http://kubernetes.io/docs/user-guide/annotations. + type: object + x-kubernetes-map-type: granular + envVars: + description: |- + envVars contain environment variables to be injected into containers. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container. + 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 + labels: + additionalProperties: + type: string + description: |- + labels is a map of string key and value pairs that can be used to organize and categorize + (scope and select) objects. + More info: http://kubernetes.io/docs/user-guide/labels. + type: object + x-kubernetes-map-type: granular + podSecurityContext: + description: |- + PodSecurityContext holds pod-level security attributes and common container settings. + Some fields are also present in container.securityContext. Field values of + container.securityContext take precedence over field values of PodSecurityContext. + 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: + + + 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---- + + + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + 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 + type: object + 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. + 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: + + + 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 + required: + - type + type: object + 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. + items: + format: int64 + type: integer + type: array + 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. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + 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. + 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 + type: object + type: object + priorityClassName: + description: priorityClassName specifies the priority class for + the pod (if any). + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + probe: + description: probe contains the fields for standard Kubernetes + readiness/liveness probe configuration. + properties: + liveness: + description: |- + liveness configures the Kubernetes probe settings. The changes + will override the existing default configuration. + properties: + failureThreshold: + description: |- + failureThreshold is the minimum consecutive failures for the probe to be considered failed. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + initialDelaySeconds: + description: |- + initialDelaySeconds is the number of seconds after the container has started and before probes are initiated. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + path: + description: Path for the HTTP probe + type: string + periodSeconds: + description: |- + periodSeconds specifies how often to perform the probe. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + port: + description: Number of the port to access on the container + type: integer + successThreshold: + description: |- + successThreshold is the minimum consecutive successes for the probe to be considered successful after having failed. + The default values is `1`. Must be `1` for liveness and startup. The minimum value is `1`. + format: int32 + type: integer + timeoutSeconds: + description: |- + timeoutSeconds is the number of seconds after which the probe times out. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + type: object + readiness: + description: |- + readiness configures the Kubernetes probe setting. The changes + will override the existing default configuration. + properties: + failureThreshold: + description: |- + failureThreshold is the minimum consecutive failures for the probe to be considered failed. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + initialDelaySeconds: + description: |- + initialDelaySeconds is the number of seconds after the container has started and before probes are initiated. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + path: + description: Path for the HTTP probe + type: string + periodSeconds: + description: |- + periodSeconds specifies how often to perform the probe. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + port: + description: Number of the port to access on the container + type: integer + successThreshold: + description: |- + successThreshold is the minimum consecutive successes for the probe to be considered successful after having failed. + The default values is `1`. Must be `1` for liveness and startup. The minimum value is `1`. + format: int32 + type: integer + timeoutSeconds: + description: |- + timeoutSeconds is the number of seconds after which the probe times out. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + type: object + type: object + resources: + description: resources describe 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. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: |- + SecurityContext holds security configuration that will be applied to a container. + Some fields are present in both SecurityContext and PodSecurityContext. When both + are set, the values in SecurityContext take precedence. + 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. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + 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 + type: object + 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. + 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: + + + 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 + required: + - type + type: object + 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. + 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 + type: object + type: object + serviceAccountName: + description: |- + ServiceAccountName is the name of the service account used to run this pod. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account. + type: string + terminationGracePeriodSeconds: + description: terminationGracePeriodSeconds is the grace period + before the pod is deleted. + format: int64 + type: integer + tolerations: + description: |- + tolerations specify the pods to schedule onto the nodes with matching taints, using + the triple `` and the matching operator ``. + 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 + topologySpreadConstraints: + description: |- + topologySpreadConstraints describe how a group of pods ought to spread across topology domains. Scheduler will + schedule pods based on the constraints. All topologySpreadConstraints are ANDed. + items: + description: TopologySpreadConstraint specifies how to spread + matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + 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. + + + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + 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. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + + + This is a beta field and requires the MinDomainsInPodTopologySpread feature gate to be enabled (enabled by default). + format: int32 + type: integer + 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. + + + 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. + + + 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 + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + rackAssignment: + description: rackAssignment specifies the rack awareness capability + of the Kafka cluster. + properties: + availabilityZoneCount: + description: |- + availabilityZoneCount configures `broker.rack` with the formula (`pod_id % azCount`). + This is mainly for backwards compatibility with Operator 1.x. + format: int32 + type: integer + nodeLabels: + description: |- + nodeLabels use the Kubernetes node API + to retrieve the label values to be used in `broker.rack`. This + feature requires CFK to run with the cluster-level access. + items: + type: string + minItems: 1 + type: array + type: object + replicas: + description: |- + replicas is the desired number of replicas. + A change to this setting will roll the cluster. + format: int32 + type: integer + services: + description: services specify the supported Kafka services. + properties: + kafkaRest: + description: kafkaRest specifies the embedded REST API server + configuration. + properties: + authentication: + description: authentication specifies the REST API server + authentication configuration. + properties: + basic: + description: basic specifies the configuration for basic + authentication. + properties: + debug: + description: debug enables the basic authentication + debug logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: OAuth specifies the configuration for OAuth + authentication. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass + the basic credential through a directory path in + the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to + pass the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication scheme + for the REST API server. Valid options are `basic` and + `mtls`. + enum: + - basic + - mtls + - oauth + type: string + required: + - type + type: object + externalAccess: + description: externalAccess specifies the external access + configuration. + properties: + loadBalancer: + description: loadBalancer specifies the configuration + to create a Kubernetes load balancer service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain is the domain name of the component + cluster. + minLength: 1 + type: string + externalTrafficPolicy: + description: externalTrafficPolicy specifies the external + traffic policy for the service. Valid options are + `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + loadBalancerSourceRanges: + description: loadBalancerSourceRanges specify the + source ranges. + items: + type: string + type: array + port: + description: |- + port specifies the external port for the client consumption. + If not configured, the same internal/external port is configured for the component. + Information about the port can be retrieved through the status API. + format: int32 + type: integer + prefix: + description: |- + prefix specify the prefix for the given domain. + The default value is the name of the cluster. + minLength: 1 + type: string + servicePorts: + description: servicePorts specify the user-provided + service port(s). + items: + description: ServicePort contains information on + service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by + this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - domain + type: object + nodePort: + description: nodePort specifies the configuration to create + a Kubernetes node port service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to `://:, where`podId` starts from `0` to `replicaCount - 1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + externalTrafficPolicy: + description: |- + externalTrafficPolicy specifies the external traffic policy for the service. + Valid options are `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + host: + description: host defines the host name of the cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + nodePortOffset: + description: |- + nodePortOffset specifies the starting offset of the node ports. The port numbers go in ascending order with respect + to the replicas count. + NodePort service creation fails if the node port is not in the range supported by the Kubernetes API server. + The default Kubernetes Node Port range is `30000` - `32762`. + format: int32 + minimum: 0 + type: integer + servicePorts: + description: |- + servicePorts specify user-provided service port(s). + For Kafka with the nodePort type, this setting is only applied to Kafka bootstrap service. + items: + description: ServicePort contains information on + service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by + this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - host + - nodePortOffset + type: object + route: + description: route specifies the configuration to create + a route service in OpenShift. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://-http-external.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain specifies the domain name of the + Confluent component cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + prefix: + description: |- + prefix specifies the component prefix when configured for the domain. + The default value is the name of the cluster. + minLength: 1 + type: string + wildcardPolicy: + description: |- + wildcardPolicy allows you to define a route that covers all hosts within a domain. Valid options are `Subdomain` and `None`. + The default value is `None`. + enum: + - Subdomain + - None + type: string + required: + - domain + type: object + type: + description: |- + type specifies the Kubernetes external service for the component. + Valid options are `loadBalancer`, `nodePort`, and `route`. + enum: + - loadBalancer + - nodePort + - route + minLength: 1 + type: string + required: + - type + type: object + listeners: + description: listeners specify the listeners configurations + for embedded REST API server. + properties: + external: + description: external specifies the Confluent component + external listener. + properties: + externalAccess: + description: externalAccess defines the external access + configuration for the Confluent component. + properties: + loadBalancer: + description: loadBalancer specifies the configuration + to create a Kubernetes load balancer service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string + key and value pairs. It specifies Kubernetes + annotations for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain is the domain name of + the component cluster. + minLength: 1 + type: string + externalTrafficPolicy: + description: externalTrafficPolicy specifies + the external traffic policy for the service. + Valid options are `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key + and value pairs. It specifies Kubernetes + labels for this service. + type: object + x-kubernetes-map-type: granular + loadBalancerSourceRanges: + description: loadBalancerSourceRanges specify + the source ranges. + items: + type: string + type: array + port: + description: |- + port specifies the external port for the client consumption. + If not configured, the same internal/external port is configured for the component. + Information about the port can be retrieved through the status API. + format: int32 + type: integer + prefix: + description: |- + prefix specify the prefix for the given domain. + The default value is the name of the cluster. + minLength: 1 + type: string + servicePorts: + description: servicePorts specify the user-provided + service port(s). + items: + description: ServicePort contains information + on service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed + by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains + the configurations of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - domain + type: object + nodePort: + description: nodePort specifies the configuration + to create a Kubernetes node port service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to `://:, where`podId` starts from `0` to `replicaCount - 1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string + key and value pairs. It specifies Kubernetes + annotations for this service. + type: object + x-kubernetes-map-type: granular + externalTrafficPolicy: + description: |- + externalTrafficPolicy specifies the external traffic policy for the service. + Valid options are `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + host: + description: host defines the host name of + the cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key + and value pairs. It specifies Kubernetes + labels for this service. + type: object + x-kubernetes-map-type: granular + nodePortOffset: + description: |- + nodePortOffset specifies the starting offset of the node ports. The port numbers go in ascending order with respect + to the replicas count. + NodePort service creation fails if the node port is not in the range supported by the Kubernetes API server. + The default Kubernetes Node Port range is `30000` - `32762`. + format: int32 + minimum: 0 + type: integer + servicePorts: + description: |- + servicePorts specify user-provided service port(s). + For Kafka with the nodePort type, this setting is only applied to Kafka bootstrap service. + items: + description: ServicePort contains information + on service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed + by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains + the configurations of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - host + - nodePortOffset + type: object + route: + description: route specifies the configuration + to create a route service in OpenShift. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://-http-external.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string + key and value pairs. It specifies Kubernetes + annotations for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain specifies the domain name + of the Confluent component cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key + and value pairs. It specifies Kubernetes + labels for this service. + type: object + x-kubernetes-map-type: granular + prefix: + description: |- + prefix specifies the component prefix when configured for the domain. + The default value is the name of the cluster. + minLength: 1 + type: string + wildcardPolicy: + description: |- + wildcardPolicy allows you to define a route that covers all hosts within a domain. Valid options are `Subdomain` and `None`. + The default value is `None`. + enum: + - Subdomain + - None + type: string + required: + - domain + type: object + type: + description: |- + type specifies the Kubernetes external service for the component. + Valid options are `loadBalancer`, `nodePort`, and `route`. + enum: + - loadBalancer + - nodePort + - route + minLength: 1 + type: string + required: + - type + type: object + tls: + description: tls specifies the TLS configuration for + the listener. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS + configuration for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret + containing the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + internal: + description: |- + internal specifies the Confluent component's internal listener. + This internal listener is for intra-communication between the pods. + properties: + port: + description: |- + port binds the given port to the internal listener. If not configured, + it will be defaulted to the component-specific internal port. + Port numbers lower than `9093` are reserved by CFK. + format: int32 + minimum: 9093 + type: integer + tls: + description: tls specifies the TLS configuration for + the listener. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS + configuration for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret + containing the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + type: object + tls: + description: tls specifies the TLS configuration for embedded + REST API server. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + mds: + description: mds specifies the MDS server configuration. + properties: + authentication: + description: authentication specifies the MDS server authentication + configuration. + properties: + type: + description: type defines the MDS authentication type. + The valid option is `bearer`. + enum: + - bearer + type: string + required: + - type + type: object + externalAccess: + description: externalAccess specifies the external access + configuration. + properties: + loadBalancer: + description: loadBalancer specifies the configuration + to create a Kubernetes load balancer service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain is the domain name of the component + cluster. + minLength: 1 + type: string + externalTrafficPolicy: + description: externalTrafficPolicy specifies the external + traffic policy for the service. Valid options are + `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + loadBalancerSourceRanges: + description: loadBalancerSourceRanges specify the + source ranges. + items: + type: string + type: array + port: + description: |- + port specifies the external port for the client consumption. + If not configured, the same internal/external port is configured for the component. + Information about the port can be retrieved through the status API. + format: int32 + type: integer + prefix: + description: |- + prefix specify the prefix for the given domain. + The default value is the name of the cluster. + minLength: 1 + type: string + servicePorts: + description: servicePorts specify the user-provided + service port(s). + items: + description: ServicePort contains information on + service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by + this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - domain + type: object + nodePort: + description: nodePort specifies the configuration to create + a Kubernetes node port service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to `://:, where`podId` starts from `0` to `replicaCount - 1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + externalTrafficPolicy: + description: |- + externalTrafficPolicy specifies the external traffic policy for the service. + Valid options are `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + host: + description: host defines the host name of the cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + nodePortOffset: + description: |- + nodePortOffset specifies the starting offset of the node ports. The port numbers go in ascending order with respect + to the replicas count. + NodePort service creation fails if the node port is not in the range supported by the Kubernetes API server. + The default Kubernetes Node Port range is `30000` - `32762`. + format: int32 + minimum: 0 + type: integer + servicePorts: + description: |- + servicePorts specify user-provided service port(s). + For Kafka with the nodePort type, this setting is only applied to Kafka bootstrap service. + items: + description: ServicePort contains information on + service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by + this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - host + - nodePortOffset + type: object + route: + description: route specifies the configuration to create + a route service in OpenShift. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://-http-external.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain specifies the domain name of the + Confluent component cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + prefix: + description: |- + prefix specifies the component prefix when configured for the domain. + The default value is the name of the cluster. + minLength: 1 + type: string + wildcardPolicy: + description: |- + wildcardPolicy allows you to define a route that covers all hosts within a domain. Valid options are `Subdomain` and `None`. + The default value is `None`. + enum: + - Subdomain + - None + type: string + required: + - domain + type: object + type: + description: |- + type specifies the Kubernetes external service for the component. + Valid options are `loadBalancer`, `nodePort`, and `route`. + enum: + - loadBalancer + - nodePort + - route + minLength: 1 + type: string + required: + - type + type: object + listeners: + description: listeners specify the listeners configurations + for MDS server. + properties: + external: + description: external specifies the Confluent component + external listener. + properties: + externalAccess: + description: externalAccess defines the external access + configuration for the Confluent component. + properties: + loadBalancer: + description: loadBalancer specifies the configuration + to create a Kubernetes load balancer service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string + key and value pairs. It specifies Kubernetes + annotations for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain is the domain name of + the component cluster. + minLength: 1 + type: string + externalTrafficPolicy: + description: externalTrafficPolicy specifies + the external traffic policy for the service. + Valid options are `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key + and value pairs. It specifies Kubernetes + labels for this service. + type: object + x-kubernetes-map-type: granular + loadBalancerSourceRanges: + description: loadBalancerSourceRanges specify + the source ranges. + items: + type: string + type: array + port: + description: |- + port specifies the external port for the client consumption. + If not configured, the same internal/external port is configured for the component. + Information about the port can be retrieved through the status API. + format: int32 + type: integer + prefix: + description: |- + prefix specify the prefix for the given domain. + The default value is the name of the cluster. + minLength: 1 + type: string + servicePorts: + description: servicePorts specify the user-provided + service port(s). + items: + description: ServicePort contains information + on service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed + by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains + the configurations of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - domain + type: object + nodePort: + description: nodePort specifies the configuration + to create a Kubernetes node port service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to `://:, where`podId` starts from `0` to `replicaCount - 1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string + key and value pairs. It specifies Kubernetes + annotations for this service. + type: object + x-kubernetes-map-type: granular + externalTrafficPolicy: + description: |- + externalTrafficPolicy specifies the external traffic policy for the service. + Valid options are `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + host: + description: host defines the host name of + the cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key + and value pairs. It specifies Kubernetes + labels for this service. + type: object + x-kubernetes-map-type: granular + nodePortOffset: + description: |- + nodePortOffset specifies the starting offset of the node ports. The port numbers go in ascending order with respect + to the replicas count. + NodePort service creation fails if the node port is not in the range supported by the Kubernetes API server. + The default Kubernetes Node Port range is `30000` - `32762`. + format: int32 + minimum: 0 + type: integer + servicePorts: + description: |- + servicePorts specify user-provided service port(s). + For Kafka with the nodePort type, this setting is only applied to Kafka bootstrap service. + items: + description: ServicePort contains information + on service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed + by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains + the configurations of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - host + - nodePortOffset + type: object + route: + description: route specifies the configuration + to create a route service in OpenShift. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://-http-external.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string + key and value pairs. It specifies Kubernetes + annotations for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain specifies the domain name + of the Confluent component cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key + and value pairs. It specifies Kubernetes + labels for this service. + type: object + x-kubernetes-map-type: granular + prefix: + description: |- + prefix specifies the component prefix when configured for the domain. + The default value is the name of the cluster. + minLength: 1 + type: string + wildcardPolicy: + description: |- + wildcardPolicy allows you to define a route that covers all hosts within a domain. Valid options are `Subdomain` and `None`. + The default value is `None`. + enum: + - Subdomain + - None + type: string + required: + - domain + type: object + type: + description: |- + type specifies the Kubernetes external service for the component. + Valid options are `loadBalancer`, `nodePort`, and `route`. + enum: + - loadBalancer + - nodePort + - route + minLength: 1 + type: string + required: + - type + type: object + tls: + description: tls specifies the TLS configuration for + the listener. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS + configuration for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret + containing the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + internal: + description: |- + internal specifies the Confluent component's internal listener. + This internal listener is for intra-communication between the pods. + properties: + port: + description: |- + port binds the given port to the internal listener. If not configured, + it will be defaulted to the component-specific internal port. + Port numbers lower than `9093` are reserved by CFK. + format: int32 + minimum: 9093 + type: integer + tls: + description: tls specifies the TLS configuration for + the listener. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS + configuration for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret + containing the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + type: object + provider: + description: provider specifies the identity provider configuration. + properties: + ldap: + description: ldap defines the LDAP service configuration. + properties: + address: + description: address defines the LDAP server address. + type: string + authentication: + description: LdapAuthentication specifies the LDAP + authentication configuration. + properties: + simple: + description: simple specifies simple authentication + configuration for the LDAP. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer defines the directory path in the container + where the credentials are mounted. + minLength: 1 + type: string + secretRef: + description: secretRef references the name + of the secret that contains the credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: type defines the authentication method + for LDAP. Valid options are `simple` and `mtls`. + enum: + - simple + - mtls + type: string + required: + - type + type: object + configurations: + description: configurations defines the LDAP configurations + for Confluent RBAC. + properties: + groupMemberAttribute: + description: groupMemberAttribute specifies the + LDAP group member attribute. + minLength: 1 + type: string + groupMemberAttributePattern: + description: groupMemberAttributePattern specifies + the regular expression pattern for the LDAP + group member attribute. + minLength: 1 + type: string + groupNameAttribute: + description: groupNameAttribute specifies the + LDAP group name attribute. + minLength: 1 + type: string + groupObjectClass: + description: groupObjectClass specifies the LDAP + group object class. + minLength: 1 + type: string + groupSearchBase: + description: groupSearchBase specifies the LDAP + search base for the group-based search. + minLength: 1 + type: string + groupSearchFilter: + description: groupSearchFilter specifies the LDAP + search filter for the group-based search. + minLength: 1 + type: string + groupSearchScope: + description: groupSearchScope specifies the LDAP + search scope for the group-based search. + format: int32 + type: integer + userMemberOfAttributePattern: + description: userMemberOfAttributePattern specifies + the regular expression pattern for the LDAP + user member attribute. + minLength: 1 + type: string + userNameAttribute: + description: userNameAttribute specifies the LDAP + username attribute. + minLength: 1 + type: string + userObjectClass: + description: userObjectClass specifies the LDAP + user object class. + minLength: 1 + type: string + userSearchBase: + description: userSearchBase specifies the LDAP + search base for the user-based search. + minLength: 1 + type: string + userSearchFilter: + description: userSearchFilter specifies the LDAP + search filter for the user-based search. + minLength: 1 + type: string + userSearchScope: + description: userSearchScope specifies the LDAP + search scope for the user-based search. + format: int32 + type: integer + type: object + tls: + description: tls specifies the TLS configuration for + the LDAP. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS + configuration for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret + containing the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + required: + - address + - authentication + - configurations + type: object + oauth: + description: oauth defines the OAuth service configuration. + properties: + configurations: + description: configurations defines the OAuth configurations. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + tls: + description: tls specifies the TLS configuration for + the OAuth IDP. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS + configuration for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret + containing the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + required: + - configurations + type: object + oidc: + description: |- + this field has been superseded with sso field + oidc defines the OIDC service configuration. + properties: + clientCredentials: + description: clientCredentials define the IDP clientID + and clientSecret. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer defines the directory path in the container + where the credentials are mounted. + minLength: 1 + type: string + secretRef: + description: secretRef references the name of + the secret that contains the credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + configurations: + description: configurations defines the OIDC configurations. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + authorizeBaseEndpointUri: + description: authorizeBaseEndpointUri specifies + the base uri for authorize endpoint. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + groupsClaimScope: + description: |- + groupsClaimScope specifies additional scope needed for the token to contain groups claim (field). + Leave this field empty (or null) if id token always contains the claims identified as groups. + minLength: 1 + type: string + issuer: + description: |- + issuer specifies the authorization server's URL. + This value should match the issuer claim ("iss") in id tokens issued by Authorization Server? + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + refreshToken: + description: refreshToken specifies whether offline_access + scope should be requested in the authorization + URI. + type: boolean + sessionMaxTimeout: + description: sessionMaxTimeout specifies the maximum + expiration time for a user's session. + format: int32 + type: integer + sessionTokenExpiry: + description: sessionTokenExpiry specifies the + validity of cookie issued by Confluent. + format: int32 + type: integer + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenBaseEndpointUri: + description: tokenBaseEndpointUri specifies the + base uri for token endpoint. + minLength: 1 + type: string + required: + - authorizeBaseEndpointUri + - issuer + - jwksEndpointUri + - refreshToken + - tokenBaseEndpointUri + type: object + enabled: + description: enabled specifies whether the SSO is + enabled. + type: boolean + required: + - clientCredentials + - configurations + - enabled + type: object + sso: + description: sso defines the SSO service configuration. + properties: + clientCredentials: + description: clientCredentials define the IDP clientID + and clientSecret. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer defines the directory path in the container + where the credentials are mounted. + minLength: 1 + type: string + secretRef: + description: secretRef references the name of + the secret that contains the credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + configurations: + description: configurations defines the OIDC configurations. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + authorizeBaseEndpointUri: + description: authorizeBaseEndpointUri specifies + the base uri for authorize endpoint. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + groupsClaimScope: + description: |- + groupsClaimScope specifies additional scope needed for the token to contain groups claim (field). + Leave this field empty (or null) if id token always contains the claims identified as groups. + minLength: 1 + type: string + issuer: + description: |- + issuer specifies the authorization server's URL. + This value should match the issuer claim ("iss") in id tokens issued by Authorization Server? + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + refreshToken: + description: refreshToken specifies whether offline_access + scope should be requested in the authorization + URI. + type: boolean + sessionMaxTimeout: + description: sessionMaxTimeout specifies the maximum + expiration time for a user's session. + format: int32 + type: integer + sessionTokenExpiry: + description: sessionTokenExpiry specifies the + validity of cookie issued by Confluent. + format: int32 + type: integer + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenBaseEndpointUri: + description: tokenBaseEndpointUri specifies the + base uri for token endpoint. + minLength: 1 + type: string + required: + - authorizeBaseEndpointUri + - issuer + - jwksEndpointUri + - refreshToken + - tokenBaseEndpointUri + type: object + enabled: + description: enabled specifies whether the SSO is + enabled. + type: boolean + required: + - clientCredentials + - configurations + - enabled + type: object + type: + description: This field has been deprecated and its value + will be ignored if set. + type: string + type: object + tls: + description: tls specifies the TLS configuration for MDS server. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + tokenKeyPair: + description: tokenKeyPair specifies the token key pair for + the MDS. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer defines the directory path in the container + where the MDS token key pair are mounted. + minLength: 1 + type: string + encryptedTokenKey: + description: |- + EncryptedTokenKey boolean value indicating whether the tokenKeypair(private used for signing) is encrypted using a passphrase. If true, cfk + operator will look for a file named mdsTokenKeyPassphrase.txt containing key value pair + mdsTokenKeyPassphrase=. Relevant only for mds server. Ignored if set for a client configuration. + type: boolean + secretRef: + description: secretRef references the name of the secret + that contains the MDS token key pair. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + required: + - provider + - tokenKeyPair + type: object + type: object + storageClass: + description: |- + storageClass specifies the user-provided storage class. If not + configured, it will use the default storage class. + properties: + name: + description: name is the storage class name. + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + telemetry: + description: telemetry specifies the Confluent telemetry reporter + configuration. + properties: + global: + description: |- + global allows disabling telemetry configuration. + If CFK is deployed with telemetry, this field is only + used to disable telemetry. The default value is `true` if + telemetry is enabled at the global level. + type: boolean + type: object + tls: + description: |- + tls specifies the global-level TLS configuration which can be used by + listeners and services. + properties: + autoGeneratedCerts: + description: |- + autoGeneratedCerts specifies that the certificates are auto-generated based on + the CA key pair provided. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + fips: + description: |- + fips specifies the configuration of FIPS compliant Bouncy Castle type Java Keystores for the cp component's + TLS settings. TLS Secrets must have the keys keystore.bcfks, truststore.bcfks, and jksPassword.txt + properties: + enabled: + description: enabled specifies whether to enable the FIPS + configuration for cp components. + type: boolean + required: + - enabled + type: object + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing the + JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + required: + - dataVolumeCapacity + - image + type: object + status: + description: status defines the observed state of the Kafka cluster. + properties: + arbitraryData: + description: arbitraryData is the map for any arbitrary data associated + with this Confluent component. + x-kubernetes-preserve-unknown-fields: true + authorizationType: + description: authorizationType is the authorization type for this + Confluent component. + type: string + brokerIdOffset: + description: brokerIdOffset is the broker id offset of the Kafka cluster. + format: int32 + type: integer + clusterID: + description: clusterID is the ID of the Kafka cluster. + type: string + clusterName: + description: clusterName is the name of the Confluent Platform component + cluster. + type: string + clusterNamespace: + description: clusterNamespace is the namespace where the Confluent + Platform component cluster is running. + type: string + conditions: + description: conditions specify the latest available observations + of the current state. + items: + description: Condition represent the latest available observations + of the current state. + properties: + lastProbeTime: + description: lastProbeTime shows the last time the condition + was evaluated. + format: date-time + type: string + lastTransitionTime: + description: lastTransitionTime shows the last time the condition + was transitioned from one status to another. + format: date-time + type: string + message: + description: message shows a human-readable message with details + about the transition. + type: string + reason: + description: reason shows the reason for the last transition + of the condition. + type: string + status: + description: status shows the status of the condition, one of + `True`, `False`, or `Unknown`. + type: string + type: + description: type shows the condition type. + type: string + type: object + type: array + currentClusterVersion: + description: currentClusterVersion is the current CP Server version + type: string + currentReplicas: + description: currentReplicas is the number of currently running replicas. + format: int32 + type: integer + internalSecrets: + description: |- + internalSecrets are internal secrets created + by CFK for this Confluent component. + items: + type: string + type: array + internalTopicNames: + description: internalTopicNames are the topics used by the component + for internal use. + items: + type: string + type: array + listeners: + additionalProperties: + properties: + advertisedExternalEndpoints: + description: advertisedExternalEndpoints specifies other advertised + endpoints used, especially for Kafka. + items: + type: string + type: array + authenticationType: + description: authenticationType shows the authentication type + configured by the listener. + type: string + client: + type: string + externalAccessType: + description: externalAccessType shows the external access type + used for the listener. + type: string + externalEndpoint: + description: externalEndpoint specifies the external endpoint + to connect to the Confluent component cluster. + type: string + internalEndpoint: + description: internalEndpoint specifies the internal endpoint + to connect to the Confluent component cluster. + type: string + tls: + description: tls shows whether TLS is configured for the listener. + type: boolean + type: object + description: listeners is a map for the status of Kafka Listeners. + type: object + x-kubernetes-map-type: granular + minISR: + description: minISR is the minimum number of in sync replicas in the + Kafka cluster. + format: int32 + type: integer + observedGeneration: + description: observedGeneration is the most recent generation observed + for this Confluent component. + format: int64 + type: integer + operatorVersion: + description: operatorVersion is the internal version of CFK. + type: string + phase: + description: |- + phase describes the state of the Confluent Platform component. This can either be 'PROVISIONING' + or 'RUNNING' + 'PROVISIONING' means the Confluent Platform component is currently getting deployed and not ready yet. + 'RUNNING' means the Confluent Platform component has been successfully deployed. + type: string + previousClusterVersion: + description: previousClusterVersion is the previous CP Server version + of the kafka cluster. + type: string + readyReplicas: + description: readyReplicas is the number of currently ready replicas. + format: int32 + type: integer + replicas: + description: replicas is the number of replicas. + format: int32 + type: integer + replicationFactor: + description: replicationFactor is the replication factor of the topics + in the Kafka cluster. + format: int32 + type: integer + selector: + description: |- + selector gets the label selector of the child pod. + The Horizontal Pod Autoscaler(HPA) will scale using the label selector of the child pod. + type: string + services: + additionalProperties: + description: ListenerStatus describes general information about + the listeners. + properties: + advertisedExternalEndpoints: + description: advertisedExternalEndpoints specifies other advertised + endpoints used, especially for Kafka. + items: + type: string + type: array + authenticationType: + description: authenticationType shows the authentication type + configured by the listener. + type: string + externalAccessType: + description: externalAccessType shows the external access type + used for the listener. + type: string + externalEndpoint: + description: externalEndpoint specifies the external endpoint + to connect to the Confluent component cluster. + type: string + internalEndpoint: + description: internalEndpoint specifies the internal endpoint + to connect to the Confluent component cluster. + type: string + tls: + description: tls shows whether TLS is configured for the listener. + type: boolean + type: object + description: services is a map for the Kafka services. + type: object + x-kubernetes-map-type: granular + zookeeperConnect: + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + scale: + labelSelectorPath: .status.selector + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + status: {} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kafkatopics.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kafkatopics.yaml new file mode 100644 index 000000000..0a71576b2 --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kafkatopics.yaml @@ -0,0 +1,410 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: kafkatopics.platform.confluent.io +spec: + group: platform.confluent.io + names: + categories: + - all + - confluent-platform + - confluent + kind: KafkaTopic + listKind: KafkaTopicList + plural: kafkatopics + shortNames: + - kt + - topic + singular: kafkatopic + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.replicas + name: Replicas + type: string + - jsonPath: .status.partitionCount + name: Partition + type: string + - jsonPath: .status.state + name: Status + type: string + - jsonPath: .status.kafkaClusterID + name: ClusterID + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.kafkaCluster + name: KafkaCluster + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: KafkaTopic is the schema for the Kafka Topic 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: spec defines the desired state of the KafkaTopic. + properties: + configs: + additionalProperties: + type: string + description: |- + configs is a map of string key and value pairs that are used to pass the configuration settings for the topic. + More info: https://docs.confluent.io/current/installation/configuration/topic-configs.html. + type: object + x-kubernetes-map-type: granular + kafkaClusterRef: + description: kafkaClusterRef specifies the name of the Kafka cluster. + properties: + name: + description: name specifies the name of the Confluent Platform + component cluster. + type: string + namespace: + description: namespace specifies the namespace where the Confluent + Platform component cluster is running. + type: string + required: + - name + type: object + kafkaRest: + description: kafkaRest specifies the Kafka REST API configuration. + properties: + authentication: + description: authentication specifies the REST API authentication + mechanism. + properties: + basic: + description: basic specifies the basic authentication settings + for the REST API client. + properties: + debug: + description: debug enables the basic authentication debug + logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + bearer: + description: bearer specifies the bearer authentication settings + for the REST API client. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: oauth specifies the OAuth authentication settings + for the REST API client. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass the + basic credential through a directory path in the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to pass + the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the REST API authentication type. + Valid options are `basic`, `bearer`, `mtls` and `oauth`. + enum: + - basic + - bearer + - mtls + - oauth + type: string + required: + - type + type: object + endpoint: + description: endpoint specifies where Confluent REST API is running. + minLength: 1 + pattern: ^https?://.* + type: string + kafkaClusterID: + description: |- + kafkaClusterID specifies the id of Kafka cluster. + It takes precedence over using the Kafka REST API to get the cluster id. + minLength: 1 + type: string + tls: + description: "tls specifies the custom TLS structure for the application + resources,\n\t// e.g. connector, topic, schema, of the Confluent + Platform components.\n\t// +optional" + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer contains the directory path in the container where + `keystore.jks`, `truststore.jks`, `jksPassword.txt` keys are mounted. + minLength: 1 + type: string + jksPassword: + description: jksPassword specifies the secret name that contains + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef specifies the secret name that contains the certificates. + More info about certificates key/value format: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: object + kafkaRestClassRef: + description: kafkaRestClassRef references the KafkaRestClass which + defines Kafka REST API connection information. + properties: + name: + description: name specifies the name of the KafkaRestClass application + resource. + minLength: 1 + type: string + namespace: + description: namespace specifies the namespace of the KafkaRestClass. + type: string + required: + - name + type: object + name: + description: |- + name specifies the topic name. If not configured, the KafkaTopic CR name is used + as the topic name. + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9\._\-]*$ + type: string + partitionCount: + description: |- + partitionCount specifies the number of partitions for the topic. + If not configured, it will be defaulted to the partition count that Kafka REST V3 API supports. + format: int32 + type: integer + replicas: + description: |- + replicas specifies the replication factor for the topic. + If not configured, it will be defaulted to the replication factor that Kafka REST V3 API supports. + format: int32 + type: integer + type: object + status: + description: status defines the observed state of the KafkaTopic. + properties: + appState: + default: Unknown + description: appState is the current state of the topic application. + enum: + - Unknown + - Created + - Failed + - Deleted + type: string + conditions: + description: conditions are the latest available observed states of + the topic. + items: + description: Condition represent the latest available observations + of the current state. + properties: + lastProbeTime: + description: lastProbeTime shows the last time the condition + was evaluated. + format: date-time + type: string + lastTransitionTime: + description: lastTransitionTime shows the last time the condition + was transitioned from one status to another. + format: date-time + type: string + message: + description: message shows a human-readable message with details + about the transition. + type: string + reason: + description: reason shows the reason for the last transition + of the condition. + type: string + status: + description: status shows the status of the condition, one of + `True`, `False`, or `Unknown`. + type: string + type: + description: type shows the condition type. + type: string + type: object + type: array + kafkaCluster: + type: string + kafkaClusterID: + description: kafkaClusterID is the id of the Kafka cluster. + type: string + kafkaRestEndpoint: + description: kafkaRestEndpoint is the endpoint of the Kafka REST API. + type: string + observedGeneration: + description: observedGeneration is the most recent generation observed + for this Confluent component. + format: int64 + type: integer + partitionCount: + description: partitionCount is the partition count of the topic. + format: int32 + type: integer + replicas: + description: replicas is the replication factor of the topic. + format: int32 + type: integer + state: + description: state is the state of the topic. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kraftcontrollers.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kraftcontrollers.yaml new file mode 100644 index 000000000..e38095c26 --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kraftcontrollers.yaml @@ -0,0 +1,5748 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: kraftcontrollers.platform.confluent.io +spec: + group: platform.confluent.io + names: + categories: + - all + - confluent-platform + - confluent + kind: KRaftController + listKind: KRaftControllerList + plural: kraftcontrollers + shortNames: + - kraftcontroller + - kraft + singular: kraftcontroller + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.replicas + name: Replicas + type: string + - jsonPath: .status.readyReplicas + name: Ready + type: string + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.controllerQuorumVoters + name: ControllerQuorumVoters + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: KRaftController is the schema for the KRaft Controller 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: spec defines the desired state of the KRaft Controller cluster. + properties: + authorization: + description: authorization specifies the authorization configuration. + properties: + superUsers: + description: |- + superUsers specify the super users to give the admin privilege on the Kafka Cluster. + This list takes the format as `User:` + items: + type: string + type: array + type: + description: type specifies the authorization type. The valid + options are `rbac` and `simple`. + enum: + - rbac + - simple + type: string + required: + - type + type: object + clusterID: + description: |- + clusterID specifies the ID of the KRaft Controller cluster. It must contain only alphanumeric characters and the + hyphen character and be of length 22. If omitted, a clusterID will be autogenerated. + In the case of attaching to existing Persistent Volumes, you must match the old clusterID. + maxLength: 22 + minLength: 22 + pattern: ^[a-zA-Z0-9\-\_]*$ + type: string + configOverrides: + description: configOverrides specifies the configs to override the + server, JVM, Log4j properties for the KRaft Controller cluster. + properties: + jvm: + description: |- + jvm is a list of JVM configuration supported by the Confluent Platform component. + This will either add or update the existing configuration. + items: + type: string + type: array + log4j: + description: |- + log4j is a list of Log4J configuration supported by the Confluent Platform component. + This will either add or update the existing configuration. + items: + type: string + type: array + server: + description: |- + server is a list of server configuration supported by the Confluent Platform component. + This will either add or update existing configuration. + items: + type: string + type: array + type: object + controllerQuorumVoters: + description: |- + QuorumVoters specify a list of kraft controllers. This is only required when deploying stretch + kafka clusters for MRC deployments in multiple DCs (or K8s clusters) and should include all the kraft controllers in other DCs that form the ensemble. + items: + description: ControllerQuorumVoter defines the KRaft controller + quorum voter. + properties: + brokerEndpoint: + description: brokerEndpoint is the endpoint of the KRaft Controller. + type: string + nodeId: + description: nodeId is the nodeId of the KRaft Controller. + format: int32 + type: integer + required: + - brokerEndpoint + - nodeId + type: object + type: array + dataVolumeCapacity: + anyOf: + - type: integer + - type: string + description: dataVolumeCapacity specifies the persistent volume capacity + for the KRaft Controller cluster. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + dependencies: + description: de + properties: + mdsKafkaCluster: + properties: + authentication: + description: authentication defines the authentication for + the Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another way + to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap + endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + tls: + description: tls defines the client-side TLS setting for the + Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + type: object + fips: + description: |- + fips specifies the configuration of FIPS compliant Bouncy Castle type Java Keystores for the KRaft Controller + cluster's TLS settings. TLS Secrets must have the keys keystore.bcfks, truststore.bcfks, and jksPassword.txt + properties: + enabled: + description: enabled specifies whether to enable the FIPS configuration + for cp components. + type: boolean + required: + - enabled + type: object + headlessService: + description: headlessService specifies the configuration of the Kubernetes + headless service. + properties: + annotations: + additionalProperties: + type: string + description: |- + annotations is a map of string key and value pairs. + It specifies the annotations to be added to the CFK-created headless service. + These annotations are merged with the injectAnnotations and take precedence. + type: object + x-kubernetes-map-type: granular + labels: + additionalProperties: + type: string + description: |- + labels is a map of string key and value pairs. + It specifies the labels to be added to the CFK-created headless service. + These labels are merged with the injectLabels and take precedence. + type: object + x-kubernetes-map-type: granular + publishNotReadyAddresses: + description: |- + publishNotReadyAddresses specifies the publishNotReadyAddresses field. + For Kafka, this value must be true. The default value is true. + type: boolean + type: object + identityProvider: + description: |- + identityProvider specifies the identity provider configuration. + It is only required for the Kafka authentication type `ldap`. + properties: + ldap: + description: ldap defines the LDAP service configuration. + properties: + address: + description: address defines the LDAP server address. + type: string + authentication: + description: LdapAuthentication specifies the LDAP authentication + configuration. + properties: + simple: + description: simple specifies simple authentication configuration + for the LDAP. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer defines the directory path in the container + where the credentials are mounted. + minLength: 1 + type: string + secretRef: + description: secretRef references the name of the + secret that contains the credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: type defines the authentication method for + LDAP. Valid options are `simple` and `mtls`. + enum: + - simple + - mtls + type: string + required: + - type + type: object + configurations: + description: configurations defines the LDAP configurations + for Confluent RBAC. + properties: + groupMemberAttribute: + description: groupMemberAttribute specifies the LDAP group + member attribute. + minLength: 1 + type: string + groupMemberAttributePattern: + description: groupMemberAttributePattern specifies the + regular expression pattern for the LDAP group member + attribute. + minLength: 1 + type: string + groupNameAttribute: + description: groupNameAttribute specifies the LDAP group + name attribute. + minLength: 1 + type: string + groupObjectClass: + description: groupObjectClass specifies the LDAP group + object class. + minLength: 1 + type: string + groupSearchBase: + description: groupSearchBase specifies the LDAP search + base for the group-based search. + minLength: 1 + type: string + groupSearchFilter: + description: groupSearchFilter specifies the LDAP search + filter for the group-based search. + minLength: 1 + type: string + groupSearchScope: + description: groupSearchScope specifies the LDAP search + scope for the group-based search. + format: int32 + type: integer + userMemberOfAttributePattern: + description: userMemberOfAttributePattern specifies the + regular expression pattern for the LDAP user member + attribute. + minLength: 1 + type: string + userNameAttribute: + description: userNameAttribute specifies the LDAP username + attribute. + minLength: 1 + type: string + userObjectClass: + description: userObjectClass specifies the LDAP user object + class. + minLength: 1 + type: string + userSearchBase: + description: userSearchBase specifies the LDAP search + base for the user-based search. + minLength: 1 + type: string + userSearchFilter: + description: userSearchFilter specifies the LDAP search + filter for the user-based search. + minLength: 1 + type: string + userSearchScope: + description: userSearchScope specifies the LDAP search + scope for the user-based search. + format: int32 + type: integer + type: object + tls: + description: tls specifies the TLS configuration for the LDAP. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + required: + - address + - authentication + - configurations + type: object + oauth: + description: oauth defines the OAuth service configuration. + properties: + configurations: + description: configurations defines the OAuth configurations. + properties: + audience: + description: audience specifies the audience claim in + the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected issuer + in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of claim + in token for identifying the groups of subject in the + JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout with + IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry backoff + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff with + IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim in JWT + to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + tls: + description: tls specifies the TLS configuration for the OAuth + IDP. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + required: + - configurations + type: object + oidc: + description: |- + this field has been superseded with sso field + oidc defines the OIDC service configuration. + properties: + clientCredentials: + description: clientCredentials define the IDP clientID and + clientSecret. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer defines the directory path in the container + where the credentials are mounted. + minLength: 1 + type: string + secretRef: + description: secretRef references the name of the secret + that contains the credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + configurations: + description: configurations defines the OIDC configurations. + properties: + audience: + description: audience specifies the audience claim in + the JWT payload. + minLength: 1 + type: string + authorizeBaseEndpointUri: + description: authorizeBaseEndpointUri specifies the base + uri for authorize endpoint. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of claim + in token for identifying the groups of subject in the + JWT payload. + minLength: 1 + type: string + groupsClaimScope: + description: |- + groupsClaimScope specifies additional scope needed for the token to contain groups claim (field). + Leave this field empty (or null) if id token always contains the claims identified as groups. + minLength: 1 + type: string + issuer: + description: |- + issuer specifies the authorization server's URL. + This value should match the issuer claim ("iss") in id tokens issued by Authorization Server? + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + refreshToken: + description: refreshToken specifies whether offline_access + scope should be requested in the authorization URI. + type: boolean + sessionMaxTimeout: + description: sessionMaxTimeout specifies the maximum expiration + time for a user's session. + format: int32 + type: integer + sessionTokenExpiry: + description: sessionTokenExpiry specifies the validity + of cookie issued by Confluent. + format: int32 + type: integer + subClaimName: + description: subClaimName specifies name of claim in JWT + to use for the subject. + minLength: 1 + type: string + tokenBaseEndpointUri: + description: tokenBaseEndpointUri specifies the base uri + for token endpoint. + minLength: 1 + type: string + required: + - authorizeBaseEndpointUri + - issuer + - jwksEndpointUri + - refreshToken + - tokenBaseEndpointUri + type: object + enabled: + description: enabled specifies whether the SSO is enabled. + type: boolean + required: + - clientCredentials + - configurations + - enabled + type: object + sso: + description: sso defines the SSO service configuration. + properties: + clientCredentials: + description: clientCredentials define the IDP clientID and + clientSecret. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer defines the directory path in the container + where the credentials are mounted. + minLength: 1 + type: string + secretRef: + description: secretRef references the name of the secret + that contains the credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + configurations: + description: configurations defines the OIDC configurations. + properties: + audience: + description: audience specifies the audience claim in + the JWT payload. + minLength: 1 + type: string + authorizeBaseEndpointUri: + description: authorizeBaseEndpointUri specifies the base + uri for authorize endpoint. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of claim + in token for identifying the groups of subject in the + JWT payload. + minLength: 1 + type: string + groupsClaimScope: + description: |- + groupsClaimScope specifies additional scope needed for the token to contain groups claim (field). + Leave this field empty (or null) if id token always contains the claims identified as groups. + minLength: 1 + type: string + issuer: + description: |- + issuer specifies the authorization server's URL. + This value should match the issuer claim ("iss") in id tokens issued by Authorization Server? + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + refreshToken: + description: refreshToken specifies whether offline_access + scope should be requested in the authorization URI. + type: boolean + sessionMaxTimeout: + description: sessionMaxTimeout specifies the maximum expiration + time for a user's session. + format: int32 + type: integer + sessionTokenExpiry: + description: sessionTokenExpiry specifies the validity + of cookie issued by Confluent. + format: int32 + type: integer + subClaimName: + description: subClaimName specifies name of claim in JWT + to use for the subject. + minLength: 1 + type: string + tokenBaseEndpointUri: + description: tokenBaseEndpointUri specifies the base uri + for token endpoint. + minLength: 1 + type: string + required: + - authorizeBaseEndpointUri + - issuer + - jwksEndpointUri + - refreshToken + - tokenBaseEndpointUri + type: object + enabled: + description: enabled specifies whether the SSO is enabled. + type: boolean + required: + - clientCredentials + - configurations + - enabled + type: object + type: + description: This field has been deprecated and its value will + be ignored if set. + type: string + type: object + image: + description: |- + image specifies the application and the init docker image configurations. + A change to this setting will roll the cluster. + properties: + application: + description: |- + application is the Docker image name of the application. Specify + `//:`. + pattern: .+:.+ + type: string + init: + description: |- + init is the init-container name. Specify + `//:`. + pattern: .+:.+ + type: string + pullPolicy: + description: |- + pullPolicy is the policy for pulling images. Valid options are `Always`, `Never`, and `IfNotPresent`. + The default value is `IfNotPresent`. + enum: + - Always + - Never + - IfNotPresent + type: string + pullSecretRef: + description: |- + pullSecretRef references the secrets in the same namespace to be used for pulling images. + Image pull secrets are distinct from secrets because secrets + can be mounted in the pod, but image pull secrets are only accessed by `kubelet`. + More info: https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod + items: + type: string + type: array + required: + - application + - init + type: object + injectAnnotations: + additionalProperties: + type: string + description: |- + injectAnnotations are the annotations injected to the internal resources that CFK created. + The internal annotations are preserved and cannot be overridden. + For pod annotations, use `podTemplate.annotations`. + type: object + x-kubernetes-map-type: granular + injectLabels: + additionalProperties: + type: string + description: |- + injectLabels are the labels injected to the internal resources that CFK created. + The internal labels are preserved and cannot be overridden. + For pod labels, use `podTemplate.labels`. + type: object + x-kubernetes-map-type: granular + k8sClusterDomain: + description: |- + k8sClusterDomain specifies the configuration of the Kubernetes cluster domain. + The default is the `cluster.local` domain. + type: string + license: + description: license specifies the license configuration for the Confluent + Platform component. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + the license key is mounted. More info: + https://docs.confluent.io/operator/current/co-license.html#update-component-level-licenses + minLength: 1 + type: string + globalLicense: + description: globalLicense specifies whether the Confluent Platform + component shares the common global license. + type: boolean + secretRef: + description: |- + secretRef references the secret that provides the license for the Confluent Platform component. + More info: https://docs.confluent.io/operator/current/co-license.html#update-component-level-licenses + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + listeners: + description: listeners specify the listeners configurations. + properties: + controller: + description: controller specifies the controller listener. + properties: + authentication: + description: authentication specifies the authentication configuration + for the listener. + properties: + jaasConfig: + description: |- + jaasConfig specifies the JaaS configuration. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: |- + jaasConfigPassThrough specifies another way to provide JaaS configuration. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + principalMappingRules: + items: + type: string + type: array + type: + description: |- + type specifies the Kafka or Zookeeper authentication type. + Valid options are `plain`, `digest`, `mtls`, `ldap` & `oauth`. + enum: + - plain + - digest + - mtls + - ldap + - oauth + type: string + required: + - type + type: object + externalAccess: + description: externalAccess defines the external access configuration + for the Kafka cluster. + properties: + loadBalancer: + description: loadBalancer specifies the configuration + to create Kubernetes load balancer services. + properties: + advertisedPort: + description: |- + advertisedPort specifies the advertised port for Kafka external access. + If not configured, it will be the same as the listener port. + Information about the advertised port can be retrieved through the status API. + format: int32 + type: integer + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + bootstrapPrefix: + description: |- + bootstrapPrefix specifies the prefix for the Kafka bootstrap advertised endpoint and will be added as `bootstrapPrefix.domain`. + The default value is the Kafka cluster name. + minLength: 1 + type: string + brokerPrefix: + description: |- + brokerPrefix specifies the prefix for the Kafka broker advertised endpoint and will be added as `brokerPrefix.domain`. + The default value is `b`, such as `b#.domain` where `#` starts from `0` to the replicas count. + minLength: 1 + type: string + domain: + description: domain is the domain name of the component + cluster. + minLength: 1 + type: string + externalTrafficPolicy: + description: externalTrafficPolicy specifies the external + traffic policy for the service. Valid options are + `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + loadBalancerSourceRanges: + description: loadBalancerSourceRanges specify the + source ranges. + items: + type: string + type: array + servicePorts: + description: servicePorts specify the user-provided + service port(s). + items: + description: ServicePort contains information on + service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by + this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - domain + type: object + nodePort: + description: nodePort specifies the configuration to create + Kubernetes node port services. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to `://:, where`podId` starts from `0` to `replicaCount - 1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + externalTrafficPolicy: + description: |- + externalTrafficPolicy specifies the external traffic policy for the service. + Valid options are `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + host: + description: host defines the host name of the cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + nodePortOffset: + description: |- + nodePortOffset specifies the starting offset of the node ports. The port numbers go in ascending order with respect + to the replicas count. + NodePort service creation fails if the node port is not in the range supported by the Kubernetes API server. + The default Kubernetes Node Port range is `30000` - `32762`. + format: int32 + minimum: 0 + type: integer + servicePorts: + description: |- + servicePorts specify user-provided service port(s). + For Kafka with the nodePort type, this setting is only applied to Kafka bootstrap service. + items: + description: ServicePort contains information on + service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by + this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - host + - nodePortOffset + type: object + route: + description: route specifies the configuration to create + route services in OpenShift. + properties: + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + bootstrapPrefix: + description: |- + bootstrapPrefix specifies the prefix for the Kafka bootstrap advertised endpoint and will be added as `bootstrapPrefix.domain`. + The default value is the Kafka cluster name. + minLength: 1 + type: string + brokerPrefix: + description: |- + brokerPrefix specifies the prefix for the Kafka broker advertised endpoint and will be added as `brokerPrefix.domain`. + The default value is `b`, such as `b#.domain` where `#` starts from `0` to the replicas count. + minLength: 1 + type: string + domain: + description: domain specifies the domain name of the + Confluent component cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + wildcardPolicy: + description: |- + wildcardPolicy allows you to define a route that covers all hosts within a domain. Valid options are `Subdomain` and `None`. + The default value is `None`. + enum: + - Subdomain + - None + type: string + required: + - domain + type: object + staticForHostBasedRouting: + description: |- + staticForHostBasedRouting enables external access by doing host based + routing through the SNI capability. + With this schema, CFK only configures Kafka advertised listeners, and no Kubernetes external + service is created. + properties: + brokerPrefix: + description: |- + brokerPrefix specifies the prefix for the broker advertised endpoints and are added as `brokerPrefix.domain`. + If not configured, it will add `b` as a prefix, such as `b#.domain` where `#` will start from `0` to the replicas count. + minLength: 1 + type: string + domain: + description: domain specifies the domain name for + the Kafka cluster. + minLength: 1 + type: string + port: + description: port specifies the port to be used in + the advertised listener for a broker. + format: int32 + type: integer + required: + - domain + - port + type: object + staticForPortBasedRouting: + description: |- + staticForPortBasedRouting enables external access by port routing. + With this schema, CFK only configures Kafka advertised listeners, and no Kubernetes external + service is created. + properties: + host: + description: host defines the host name to be used + in the advertised listener for a broker. + minLength: 1 + type: string + portOffset: + description: |- + portOffset specifies the starting port number. The port numbers go in ascending order with + respect to the replicas count. + format: int32 + type: integer + required: + - host + - portOffset + type: object + type: + description: |- + type specifies the Kubernetes service for external access. + Valid options are `loadBalancer`, `nodePort`, `route`, `staticForPortBasedRouting`, and `staticForHostBasedRouting`. + enum: + - loadBalancer + - nodePort + - route + - staticForPortBasedRouting + - staticForHostBasedRouting + type: string + required: + - type + type: object + tls: + description: tls specifies the TLS configuration for the listener. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + type: object + metricReporter: + description: |- + metricsReporter specifies the configuration of the metric reporter. The metric reporter is enabled by default. + If authentication and TLS are not set, the metrics reporter uses internal listener's authentication and TLS . + properties: + authentication: + description: authentication specifies the Kafka client-side authentication + configuration. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side JaaS + configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another way to + provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim in + the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected issuer + in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of claim + in token for identifying the groups of subject in the + JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout with + IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry backoff + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff with + IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim in JWT + to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap endpoint. + type: string + enabled: + description: enabled specifies whether to enable or disable the + metric reporter. + type: boolean + replicationFactor: + description: replicationFactor specifies the number of replicas + in the metric topic. + format: int32 + type: integer + tls: + description: tls specifies the Kafka client-side TLS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + required: + - enabled + type: object + metrics: + description: metrics specify the security settings for the metric + services. + properties: + authentication: + description: authentication specifies the authentication configuration + for the metrics. + properties: + type: + description: type specifies the metrics authentication method. + The valid option is `mtls`. + enum: + - mtls + type: string + required: + - type + type: object + prometheus: + description: prometheus specifies the configuration overrides + for the JMX-Prometheus exporter. + properties: + blacklist: + items: + type: string + type: array + rules: + items: + description: Rule defines the Prometheus Exporter rule override. + properties: + attrNameSnakeCase: + type: boolean + cache: + type: boolean + help: + minLength: 1 + type: string + labels: + additionalProperties: + type: string + type: object + x-kubernetes-map-type: granular + name: + minLength: 1 + type: string + pattern: + minLength: 1 + type: string + type: + minLength: 1 + type: string + value: + minLength: 1 + type: string + valueFactor: + anyOf: + - type: integer + - type: string + default: 1 + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: array + whitelist: + items: + type: string + type: array + type: object + tls: + description: tls specifies the TLS configuration for the metrics. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + mountedSecrets: + description: |- + mountedSecrets list the secrets injected to + the underlying statefulset configuration. The secret reference is mounted + in the default path `/mnt/secrets/`. The underlying resources + will follow the secret as a file configuration. + More info: https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets-as-files-from-a-pod. + A change to this setting will roll the cluster. + items: + description: |- + MountedSecrets provides a way to inject a custom secret to the underlying + statefulset. + properties: + keyItems: + description: keyItems are key and path names. + items: + description: Maps a string key to a path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + secretRef: + description: secretRef references the name of the secret. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + type: array + mountedVolumes: + description: |- + mountedVolumes list the custom volumes that need to be mounted into the + underlying statefulset. + A change to this setting will roll the cluster. + properties: + volumeMounts: + description: |- + volumeMounts specify the list of volume mounts for the pods in the + statefulset. + items: + description: VolumeMount describes a mounting of a Volume within + a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + description: |- + volumes specify the list of volumes that can be mounted into the pods + of statefulset. + items: + description: Volume represents a named volume in a pod that + may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + format: int32 + type: integer + readOnly: + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: boolean + volumeID: + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + required: + - volumeID + type: object + azureDisk: + description: azureDisk represents an Azure Data Disk mount + on the host and bind mount to the pod. + properties: + cachingMode: + description: 'cachingMode is the Host Caching mode: + None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the data disk in + the blob storage + type: string + diskURI: + description: diskURI is the URI of data disk in the + blob storage + type: string + fsType: + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: multiple + blob disks per storage account Dedicated: single + blob disk per storage account Managed: azure managed + data disk (only in managed availability set). defaults + to shared' + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: azureFile represents an Azure File Service + mount on the host and bind mount to the pod. + properties: + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of secret that + contains Azure Storage Account Name and Key + type: string + shareName: + description: shareName is the azure share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: cephFS represents a Ceph FS mount on the host + that shares a pod's lifetime + properties: + monitors: + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + items: + type: string + type: array + path: + description: 'path is Optional: Used as the mounted + root, rather than the full Ceph tree, default is /' + type: string + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: boolean + secretFile: + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + secretRef: + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + required: + - monitors + type: object + cinder: + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: boolean + secretRef: + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + description: csi (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers (Beta feature). + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward API about the + pod that should populate this volume + properties: + defaultMode: + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents information + to create the file containing the pod field + properties: + fieldRef: + description: 'Required: Selects a field of the + pod: only annotations, labels, name and namespace + are supported.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must not + be absolute or contain the ''..'' path. Must + be utf-8 encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + emptyDir: + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + properties: + medium: + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: 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]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. + properties: + volumeClaimTemplate: + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + + Required, must not be nil. + properties: + metadata: + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. + type: object + spec: + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim + are also valid here. + properties: + accessModes: + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + items: + type: string + type: array + dataSource: + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + 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. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any 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. + 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 + required: + - kind + - name + type: object + resources: + description: |- + resources represents the minimum resources the volume should have. + If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + selector: + description: selector is a label query over + volumes to consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + type: string + volumeAttributesClassName: + description: |- + volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. + If specified, the CSI driver will create or update the volume with the attributes defined + in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, + it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass + will be applied to the claim but it's not allowed to reset this field to empty string once it is set. + If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass + will be set by the persistentvolume controller if it exists. + If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be + set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource + exists. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#volumeattributesclass + (Alpha) Using this field requires the VolumeAttributesClass feature gate to be enabled. + type: string + volumeMode: + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding reference + to the PersistentVolume backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource that + is attached to a kubelet's host machine and then exposed + to the pod. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + lun: + description: 'lun is Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC target worldwide + names (WWNs)' + items: + type: string + type: array + wwids: + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. + items: + type: string + type: array + type: object + flexVolume: + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. + properties: + driver: + description: driver is the name of the driver to use + for this volume. + type: string + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this field holds + extra command options if any.' + type: object + readOnly: + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + description: flocker represents a Flocker volume attached + to a kubelet's host machine. This depends on the Flocker + control service being running + properties: + datasetName: + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of the dataset. + This is unique identifier of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + properties: + fsType: + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + format: int32 + type: integer + pdName: + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: boolean + required: + - pdName + type: object + gitRepo: + description: |- + gitRepo represents a git repository at a particular revision. + DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. + properties: + directory: + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash for the specified + revision. + type: string + required: + - repository + type: object + glusterfs: + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/glusterfs/README.md + properties: + endpoints: + description: |- + endpoints is the endpoint name that details Glusterfs topology. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + path: + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + readOnly: + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- + TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not + mount host directories as read/write. + properties: + path: + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + type: + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + required: + - path + type: object + iscsi: + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://examples.k8s.io/volumes/iscsi/README.md + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether support + iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether support + iSCSI Session CHAP authentication + type: boolean + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + initiatorName: + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified Name. + type: string + iscsiInterface: + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret for iSCSI + target and initiator authentication + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + nfs: + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + properties: + path: + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + readOnly: + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: boolean + server: + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + properties: + claimName: + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + type: string + readOnly: + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: photonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets host + machine + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies Photon Controller + persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: portworxVolume represents a portworx volume + attached and mounted on kubelets host machine + properties: + fsType: + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one resources secrets, + configmaps, and downward API + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + sources: + description: sources is the list of volume projections + items: + description: Projection that may be projected along + with other supported volume types + properties: + clusterTrustBundle: + description: |- + ClusterTrustBundle allows a pod to access the `.spec.trustBundle` field + of ClusterTrustBundle objects in an auto-updating file. + + + Alpha, gated by the ClusterTrustBundleProjection feature gate. + + + ClusterTrustBundle objects can either be selected by name, or by the + combination of signer name and a label selector. + + + Kubelet performs aggressive normalization of the PEM contents written + into the pod filesystem. Esoteric PEM features such as inter-block + comments and block headers are stripped. Certificates are deduplicated. + The ordering of certificates within the file is arbitrary, and Kubelet + may change the order over time. + properties: + labelSelector: + description: |- + Select all ClusterTrustBundles that match this label selector. Only has + effect if signerName is set. Mutually-exclusive with name. If unset, + interpreted as "match nothing". If set but empty, interpreted as "match + everything". + 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 + name: + description: |- + Select a single ClusterTrustBundle by object name. Mutually-exclusive + with signerName and labelSelector. + type: string + optional: + description: |- + If true, don't block pod startup if the referenced ClusterTrustBundle(s) + aren't available. If using name, then the named ClusterTrustBundle is + allowed not to exist. If using signerName, then the combination of + signerName and labelSelector is allowed to match zero + ClusterTrustBundles. + type: boolean + path: + description: Relative path from the volume + root to write the bundle. + type: string + signerName: + description: |- + Select all ClusterTrustBundles that match this signer name. + Mutually-exclusive with name. The contents of all selected + ClusterTrustBundles will be unified and deduplicated. + type: string + required: + - path + type: object + configMap: + description: configMap information about the configMap + data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional specify whether the + ConfigMap or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about the + downwardAPI data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a field + of the pod: only annotations, labels, + name and namespace are supported.' + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in terms + of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to + select in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file to + be created. Must not be absolute or + contain the ''..'' path. Must be utf-8 + encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env + vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + secret: + description: secret information about the secret + data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional field specify whether + the Secret or its key must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information + about the serviceAccountToken data to project + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + format: int64 + type: integer + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + description: quobyte represents a Quobyte mount on the host + that shares a pod's lifetime + properties: + group: + description: |- + group to map volume access to + Default is no group + type: string + readOnly: + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes + type: string + tenant: + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: |- + user to map volume access to + Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references an already + created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/rbd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + image: + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + keyring: + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + monitors: + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + items: + type: string + type: array + pool: + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: boolean + secretRef: + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + required: + - image + - monitors + type: object + scaleIO: + description: scaleIO represents a ScaleIO persistent volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". + type: string + gateway: + description: gateway is the host address of the ScaleIO + API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name of the ScaleIO + Protection Domain for the configured storage. + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + description: sslEnabled Flag enable/disable SSL communication + with Gateway, default false + type: boolean + storageMode: + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO Storage Pool + associated with the protection domain. + type: string + system: + description: system is the name of the storage system + as configured in ScaleIO. + type: string + volumeName: + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: optional field specify whether the Secret + or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + type: object + storageos: + description: storageOS represents a StorageOS volume attached + and mounted on Kubernetes nodes. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. + type: string + volumeNamespace: + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: vsphereVolume represents a vSphere volume attached + and mounted on kubelets host machine + properties: + fsType: + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage Policy Based + Management (SPBM) profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage Policy + Based Management (SPBM) profile name. + type: string + volumePath: + description: volumePath is the path that identifies + vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + required: + - volumeMounts + - volumes + type: object + oneReplicaPerNode: + description: |- + oneReplicaPerNode controls whether to run 1 pod per node using the pod anti-affinity capability. + Enabling this configuration in an existing cluster will roll the cluster. + type: boolean + pdb: + description: |- + configures PodDisruptionBudget for the Confluent Platform component. + by default PDB is configured based on pre-detemined formula. + properties: + enabled: + description: enabled specifies whether the PodDisruptionBudget + is enabled + type: boolean + maxUnavailable: + description: maxUnavailable is the maximum number of pods that + can be unavailable during the disruption. + format: int32 + type: integer + required: + - enabled + type: object + podTemplate: + description: podTemplate specifies the statefulset pod template configuration. + properties: + affinity: + description: |- + affinity specifies a group of affinity scheduling rules. + More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + type: array + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + annotations: + additionalProperties: + type: string + description: |- + annotations is a map of string key and value pairs stored with the resource and + may be set by external tools to store and retrieve arbitrary metadata. They + are not queryable and should be preserved when modifying objects. More + info: http://kubernetes.io/docs/user-guide/annotations. + type: object + x-kubernetes-map-type: granular + envVars: + description: |- + envVars contain environment variables to be injected into containers. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container. + 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 + labels: + additionalProperties: + type: string + description: |- + labels is a map of string key and value pairs that can be used to organize and categorize + (scope and select) objects. + More info: http://kubernetes.io/docs/user-guide/labels. + type: object + x-kubernetes-map-type: granular + podSecurityContext: + description: |- + PodSecurityContext holds pod-level security attributes and common container settings. + Some fields are also present in container.securityContext. Field values of + container.securityContext take precedence over field values of PodSecurityContext. + 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: + + + 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---- + + + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + 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 + type: object + 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. + 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: + + + 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 + required: + - type + type: object + 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. + items: + format: int64 + type: integer + type: array + 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. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + 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. + 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 + type: object + type: object + priorityClassName: + description: priorityClassName specifies the priority class for + the pod (if any). + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + probe: + description: probe contains the fields for standard Kubernetes + readiness/liveness probe configuration. + properties: + liveness: + description: |- + liveness configures the Kubernetes probe settings. The changes + will override the existing default configuration. + properties: + failureThreshold: + description: |- + failureThreshold is the minimum consecutive failures for the probe to be considered failed. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + initialDelaySeconds: + description: |- + initialDelaySeconds is the number of seconds after the container has started and before probes are initiated. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + path: + description: Path for the HTTP probe + type: string + periodSeconds: + description: |- + periodSeconds specifies how often to perform the probe. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + port: + description: Number of the port to access on the container + type: integer + successThreshold: + description: |- + successThreshold is the minimum consecutive successes for the probe to be considered successful after having failed. + The default values is `1`. Must be `1` for liveness and startup. The minimum value is `1`. + format: int32 + type: integer + timeoutSeconds: + description: |- + timeoutSeconds is the number of seconds after which the probe times out. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + type: object + readiness: + description: |- + readiness configures the Kubernetes probe setting. The changes + will override the existing default configuration. + properties: + failureThreshold: + description: |- + failureThreshold is the minimum consecutive failures for the probe to be considered failed. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + initialDelaySeconds: + description: |- + initialDelaySeconds is the number of seconds after the container has started and before probes are initiated. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + path: + description: Path for the HTTP probe + type: string + periodSeconds: + description: |- + periodSeconds specifies how often to perform the probe. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + port: + description: Number of the port to access on the container + type: integer + successThreshold: + description: |- + successThreshold is the minimum consecutive successes for the probe to be considered successful after having failed. + The default values is `1`. Must be `1` for liveness and startup. The minimum value is `1`. + format: int32 + type: integer + timeoutSeconds: + description: |- + timeoutSeconds is the number of seconds after which the probe times out. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + type: object + type: object + resources: + description: resources describe 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. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: |- + SecurityContext holds security configuration that will be applied to a container. + Some fields are present in both SecurityContext and PodSecurityContext. When both + are set, the values in SecurityContext take precedence. + 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. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + 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 + type: object + 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. + 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: + + + 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 + required: + - type + type: object + 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. + 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 + type: object + type: object + serviceAccountName: + description: |- + ServiceAccountName is the name of the service account used to run this pod. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account. + type: string + terminationGracePeriodSeconds: + description: terminationGracePeriodSeconds is the grace period + before the pod is deleted. + format: int64 + type: integer + tolerations: + description: |- + tolerations specify the pods to schedule onto the nodes with matching taints, using + the triple `` and the matching operator ``. + 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 + topologySpreadConstraints: + description: |- + topologySpreadConstraints describe how a group of pods ought to spread across topology domains. Scheduler will + schedule pods based on the constraints. All topologySpreadConstraints are ANDed. + items: + description: TopologySpreadConstraint specifies how to spread + matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + 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. + + + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + 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. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + + + This is a beta field and requires the MinDomainsInPodTopologySpread feature gate to be enabled (enabled by default). + format: int32 + type: integer + 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. + + + 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. + + + 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 + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + replicas: + description: |- + replicas is the desired number of replicas. + A change to this setting will roll the cluster. + format: int32 + type: integer + storageClass: + description: |- + storageClass specifies the user-provided storage class. If not + configured, it will use the default storage class. + properties: + name: + description: name is the storage class name. + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + telemetry: + description: telemetry specifies the Confluent telemetry reporter + configuration. + properties: + global: + description: |- + global allows disabling telemetry configuration. + If CFK is deployed with telemetry, this field is only + used to disable telemetry. The default value is `true` if + telemetry is enabled at the global level. + type: boolean + type: object + tls: + description: |- + tls specifies the global-level TLS configuration which can be used by + listeners and services. + properties: + autoGeneratedCerts: + description: |- + autoGeneratedCerts specifies that the certificates are auto-generated based on + the CA key pair provided. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + fips: + description: |- + fips specifies the configuration of FIPS compliant Bouncy Castle type Java Keystores for the cp component's + TLS settings. TLS Secrets must have the keys keystore.bcfks, truststore.bcfks, and jksPassword.txt + properties: + enabled: + description: enabled specifies whether to enable the FIPS + configuration for cp components. + type: boolean + required: + - enabled + type: object + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing the + JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + required: + - dataVolumeCapacity + - image + type: object + status: + description: status defines the observed state of the KRaft Controller + cluster. + properties: + arbitraryData: + description: arbitraryData is the map for any arbitrary data associated + with this Confluent component. + x-kubernetes-preserve-unknown-fields: true + authorizationType: + description: authorizationType is the authorization type for this + Confluent component. + type: string + brokerIdOffset: + description: brokerIdOffset is the broker id offset of the KRaft Controller + cluster. + format: int32 + type: integer + clusterID: + description: clusterID is the ID of the KRaft Controller cluster. + type: string + clusterName: + description: clusterName is the name of the Confluent Platform component + cluster. + type: string + clusterNamespace: + description: clusterNamespace is the namespace where the Confluent + Platform component cluster is running. + type: string + conditions: + description: conditions specify the latest available observations + of the current state. + items: + description: Condition represent the latest available observations + of the current state. + properties: + lastProbeTime: + description: lastProbeTime shows the last time the condition + was evaluated. + format: date-time + type: string + lastTransitionTime: + description: lastTransitionTime shows the last time the condition + was transitioned from one status to another. + format: date-time + type: string + message: + description: message shows a human-readable message with details + about the transition. + type: string + reason: + description: reason shows the reason for the last transition + of the condition. + type: string + status: + description: status shows the status of the condition, one of + `True`, `False`, or `Unknown`. + type: string + type: + description: type shows the condition type. + type: string + type: object + type: array + controllerQuorumVoters: + description: controllerQuorumVoters is the list KRaft Controller Quorum + Voters. + items: + description: ControllerQuorumVoter defines the KRaft controller + quorum voter. + properties: + brokerEndpoint: + description: brokerEndpoint is the endpoint of the KRaft Controller. + type: string + nodeId: + description: nodeId is the nodeId of the KRaft Controller. + format: int32 + type: integer + required: + - brokerEndpoint + - nodeId + type: object + type: array + currentReplicas: + description: currentReplicas is the number of currently running replicas. + format: int32 + type: integer + internalSecrets: + description: |- + internalSecrets are internal secrets created + by CFK for this Confluent component. + items: + type: string + type: array + internalTopicNames: + description: internalTopicNames are the topics used by the component + for internal use. + items: + type: string + type: array + listeners: + additionalProperties: + description: ListenerStatus describes general information about + the listeners. + properties: + advertisedExternalEndpoints: + description: advertisedExternalEndpoints specifies other advertised + endpoints used, especially for Kafka. + items: + type: string + type: array + authenticationType: + description: authenticationType shows the authentication type + configured by the listener. + type: string + externalAccessType: + description: externalAccessType shows the external access type + used for the listener. + type: string + externalEndpoint: + description: externalEndpoint specifies the external endpoint + to connect to the Confluent component cluster. + type: string + internalEndpoint: + description: internalEndpoint specifies the internal endpoint + to connect to the Confluent component cluster. + type: string + tls: + description: tls shows whether TLS is configured for the listener. + type: boolean + type: object + description: listeners is a map for the status of Kafka Listeners. + type: object + x-kubernetes-map-type: granular + observedGeneration: + description: observedGeneration is the most recent generation observed + for this Confluent component. + format: int64 + type: integer + operatorVersion: + description: operatorVersion is the internal version of CFK. + type: string + phase: + description: |- + phase describes the state of the Confluent Platform component. This can either be 'PROVISIONING' + or 'RUNNING' + 'PROVISIONING' means the Confluent Platform component is currently getting deployed and not ready yet. + 'RUNNING' means the Confluent Platform component has been successfully deployed. + type: string + readyReplicas: + description: readyReplicas is the number of currently ready replicas. + format: int32 + type: integer + replicas: + description: replicas is the number of replicas. + format: int32 + type: integer + selector: + description: |- + selector gets the label selector of the child pod. + The Horizontal Pod Autoscaler(HPA) will scale using the label selector of the child pod. + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + scale: + labelSelectorPath: .status.selector + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + status: {} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kraftmigrationjobs.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kraftmigrationjobs.yaml new file mode 100644 index 000000000..b9a054fae --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_kraftmigrationjobs.yaml @@ -0,0 +1,180 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: kraftmigrationjobs.platform.confluent.io +spec: + group: platform.confluent.io + names: + categories: + - all + - confluent-platform + - confluent + kind: KRaftMigrationJob + listKind: KRaftMigrationJobList + plural: kraftmigrationjobs + shortNames: + - kraftmigrationjob + - kmj + singular: kraftmigrationjob + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: KRaftMigrationJob is the schema for the KRaftMigrationJob 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: spec defines the desired state of the KRaftMigrationJob. + properties: + dependencies: + description: dependencies specify the Kafka Broker, Zookeeper and + KRaft Controllers. + properties: + kRaftController: + description: |- + kRaftController specifies the dependency configuration for the KRaftController cluster. + You cannot configure both zookeeper and kRaftController dependencies. + properties: + name: + description: name specifies the name of the Confluent Platform + component cluster. + type: string + namespace: + description: namespace specifies the namespace where the Confluent + Platform component cluster is running. + type: string + required: + - name + type: object + kafka: + description: kafka defines the Kafka dependency configurations. + properties: + name: + description: name specifies the name of the Confluent Platform + component cluster. + type: string + namespace: + description: namespace specifies the namespace where the Confluent + Platform component cluster is running. + type: string + required: + - name + type: object + zookeeper: + description: zookeeper specifies the dependency configuration + for Zookeeper. + properties: + name: + description: name specifies the name of the Confluent Platform + component cluster. + type: string + namespace: + description: namespace specifies the namespace where the Confluent + Platform component cluster is running. + type: string + required: + - name + type: object + required: + - kRaftController + - kafka + - zookeeper + type: object + required: + - dependencies + type: object + status: + description: status defines the observed state of the KRaftMigrationJob. + properties: + conditions: + description: conditions represents the latest available observations + of the kraft migration job. + items: + description: Condition represent the latest available observations + of the current state. + properties: + lastProbeTime: + description: lastProbeTime shows the last time the condition + was evaluated. + format: date-time + type: string + lastTransitionTime: + description: lastTransitionTime shows the last time the condition + was transitioned from one status to another. + format: date-time + type: string + message: + description: message shows a human-readable message with details + about the transition. + type: string + reason: + description: reason shows the reason for the last transition + of the condition. + type: string + status: + description: status shows the status of the condition, one of + `True`, `False`, or `Unknown`. + type: string + type: + description: type shows the condition type. + type: string + type: object + type: array + kafkaClusterId: + description: clusterId is the clusterId for migrating cluster + type: string + phase: + description: phase is the state of the kraft migration job. + type: string + subPhase: + description: subPhase is the state of the kraft migration job. + type: string + zkEndpointWithNode: + description: |- + zkEndpointWithNode is the zkEndpoint with node fetched from kafka + / + type: string + required: + - kafkaClusterId + - phase + - subPhase + - zkEndpointWithNode + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + scale: + labelSelectorPath: .status.selector + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + status: {} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_ksqldbs.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_ksqldbs.yaml new file mode 100644 index 000000000..282179eac --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_ksqldbs.yaml @@ -0,0 +1,6646 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: ksqldbs.platform.confluent.io +spec: + group: platform.confluent.io + names: + categories: + - all + - confluent-platform + - confluent + kind: KsqlDB + listKind: KsqlDBList + plural: ksqldbs + shortNames: + - ksqldb + - ksql + singular: ksqldb + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.replicas + name: Replicas + type: string + - jsonPath: .status.readyReplicas + name: Ready + type: string + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.kafka.bootstrapEndpoint + name: Kafka + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: KsqlDB is the schema for the ksqlDB 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: spec defines the desired state of the ksqlDB cluster. + properties: + authentication: + description: authentication specifies whether authentication is needed + when accessing the ksqlDB cluster. + properties: + basic: + description: basic specifies the configuration for basic authentication. + properties: + debug: + description: debug enables the basic authentication debug + logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: OAuth specifies the configuration for OAuth authentication. + properties: + configuration: + description: configuration specifies the OAuth server settings. + properties: + audience: + description: audience specifies the audience claim in + the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected issuer + in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of claim + in token for identifying the groups of subject in the + JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout with + IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry backoff + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff with + IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim in JWT + to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass the basic + credential through a directory path in the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to pass the + required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication scheme for the + REST API server. Valid options are `basic` and `mtls`. + enum: + - basic + - mtls + - oauth + type: string + required: + - type + type: object + authorization: + description: authorization specifies the RBAC configuration for the + ksqlDB cluster. + properties: + kafkaRestClassRef: + description: |- + kafkaRestClassRef references the KafkaRestClass + which specifies the Kafka REST API connection configuration. + properties: + name: + description: name specifies the name of the KafkaRestClass + application resource. + minLength: 1 + type: string + namespace: + description: namespace specifies the namespace of the KafkaRestClass. + type: string + required: + - name + type: object + type: + description: type specifies the client-side authorization type. + The valid option is `rbac`. + enum: + - rbac + type: string + required: + - type + type: object + configOverrides: + description: |- + configOverrides specifies the configs to override the server, JVM, Log4j properties for the ksqlDB cluster. + A change will roll the cluster. + properties: + jvm: + description: |- + jvm is a list of JVM configuration supported by the Confluent Platform component. + This will either add or update the existing configuration. + items: + type: string + type: array + log4j: + description: |- + log4j is a list of Log4J configuration supported by the Confluent Platform component. + This will either add or update the existing configuration. + items: + type: string + type: array + server: + description: |- + server is a list of server configuration supported by the Confluent Platform component. + This will either add or update existing configuration. + items: + type: string + type: array + type: object + dataVolumeCapacity: + anyOf: + - type: integer + - type: string + description: dataVolumeCapacity specifies the data volume for the + ksqlDB cluster. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + dependencies: + description: dependencies specifies the dependency configurations + for Kafka, Interceptor, Schema Registry, the MDS, and Connect. + properties: + connect: + description: connect specifies the Connect dependency configuration. + properties: + authentication: + description: authentication specifies the authentication configuration + for the Connect cluster. + properties: + basic: + description: basic specifies the configuration for basic + authentication. + properties: + debug: + description: debug enables the basic authentication + debug logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: OAuth specifies the configuration for OAuth + authentication. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass + the basic credential through a directory path in + the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to + pass the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication scheme + for the REST API client. Valid options are `basic` and + `mtls`. + enum: + - basic + - mtls + - oauth + type: string + required: + - type + type: object + tls: + description: tls specifies the client-side TLS setting for + the Connect cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + url: + description: url specifies the URL endpoint of the Connect + cluster. + minLength: 1 + pattern: ^https?://.* + type: string + required: + - url + type: object + interceptor: + description: interceptor specifies the interceptor dependency + configuration. + properties: + configs: + description: |- + configs describe the configurations for the Confluent Platform interceptor. + The config override feature can be used to pass the configuration settings. + items: + type: string + type: array + consumer: + description: |- + consumer specifies the consumer configuration for the interceptor. If not + configured, it uses the Kafka dependency configuration. + properties: + authentication: + description: authentication defines the authentication + for the Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another + way to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap + endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + discovery: + description: discovery specifies the capability to discover + the Kafka cluster. + properties: + name: + description: name is the name of the Confluent Platform + component cluster. + type: string + namespace: + description: |- + namespace is where the Confluent Platform component is running. + The default value is the namespace where CFK is running. + type: string + secretRef: + description: secretRef is the name of the secret used + to discover the Confluent Platform component. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + tls: + description: tls defines the client-side TLS setting for + the Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + enabled: + description: enabled indicates whether the Confluent Platform + interceptor is enabled or disabled. + type: boolean + producer: + description: |- + producer specifies the producer configuration for the interceptor. If not + configured, it uses the Kafka dependency configuration. + properties: + authentication: + description: authentication defines the authentication + for the Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another + way to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap + endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + discovery: + description: discovery specifies the capability to discover + the Kafka cluster. + properties: + name: + description: name is the name of the Confluent Platform + component cluster. + type: string + namespace: + description: |- + namespace is where the Confluent Platform component is running. + The default value is the namespace where CFK is running. + type: string + secretRef: + description: secretRef is the name of the secret used + to discover the Confluent Platform component. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + tls: + description: tls defines the client-side TLS setting for + the Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + publishMs: + type: integer + required: + - enabled + type: object + kafka: + description: kafka specifies the Kafka dependency configuration. + properties: + authentication: + description: authentication defines the authentication for + the Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another way + to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap + endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + discovery: + description: discovery specifies the capability to discover + the Kafka cluster. + properties: + name: + description: name is the name of the Confluent Platform + component cluster. + type: string + namespace: + description: |- + namespace is where the Confluent Platform component is running. + The default value is the namespace where CFK is running. + type: string + secretRef: + description: secretRef is the name of the secret used + to discover the Confluent Platform component. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + tls: + description: tls defines the client-side TLS setting for the + Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + mds: + description: mds specifies the MDS dependencies configuration. + properties: + authentication: + description: authentication specifies the client side authentication + configuration for the MDS. + properties: + bearer: + description: bearer specifies the bearer authentication + settings. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: oauth specifies the OAuth authentication + settings. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass + the basic credential through a directory path in + the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to + pass the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication method + for the MDS. The valid option is `bearer`, `oauth`. + enum: + - bearer + - oauth + type: string + required: + - type + type: object + endpoint: + description: endpoint specifies the MDS endpoint. + minLength: 1 + pattern: ^https?://.* + type: string + ssoProtocol: + description: sso protocol, valid options are ldap and oidc. + enum: + - ldap + - oidc + type: string + tls: + description: ClientTLSConfig specifies the TLS configuration + for the Confluent component (dependencies, listeners). + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + tokenKeyPair: + description: tokenKeyPair specifies the token keypair to configure + the MDS. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer defines the directory path in the container + where the MDS token key pair are mounted. + minLength: 1 + type: string + encryptedTokenKey: + description: |- + EncryptedTokenKey boolean value indicating whether the tokenKeypair(private used for signing) is encrypted using a passphrase. If true, cfk + operator will look for a file named mdsTokenKeyPassphrase.txt containing key value pair + mdsTokenKeyPassphrase=. Relevant only for mds server. Ignored if set for a client configuration. + type: boolean + secretRef: + description: secretRef references the name of the secret + that contains the MDS token key pair. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + required: + - authentication + - endpoint + - tokenKeyPair + type: object + schemaRegistry: + description: schemaRegistry specifies the Schema Registry dependency + configuration. + properties: + authentication: + description: authentication specifies the authentication for + the Schema Registry cluster. + properties: + basic: + description: basic specifies the configuration for basic + authentication. + properties: + debug: + description: debug enables the basic authentication + debug logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: OAuth specifies the configuration for OAuth + authentication. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass + the basic credential through a directory path in + the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to + pass the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication scheme + for the REST API client. Valid options are `basic` and + `mtls`. + enum: + - basic + - mtls + - oauth + type: string + required: + - type + type: object + tls: + description: tls defines the client-side TLS setting for the + Schema Registry cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + url: + description: url specifies the URL endpoint of the Schema + Registry cluster. + minLength: 1 + pattern: ^https?://.* + type: string + required: + - url + type: object + type: object + externalAccess: + description: |- + externalAccess specifies the configurations for the endpoints and services to make the ksqlDB + accessible from outside the cluster. + When `spec.listeners` is configured, configuring `spec.externalAccess` is not allowed. + Please configure `spec.listeners.external.externalAccess` instead". + properties: + loadBalancer: + description: loadBalancer specifies the configuration to create + a Kubernetes load balancer service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and value + pairs. It specifies Kubernetes annotations for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain is the domain name of the component cluster. + minLength: 1 + type: string + externalTrafficPolicy: + description: externalTrafficPolicy specifies the external + traffic policy for the service. Valid options are `Local` + and `Cluster`. + enum: + - Local + - Cluster + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value pairs. + It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + loadBalancerSourceRanges: + description: loadBalancerSourceRanges specify the source ranges. + items: + type: string + type: array + port: + description: |- + port specifies the external port for the client consumption. + If not configured, the same internal/external port is configured for the component. + Information about the port can be retrieved through the status API. + format: int32 + type: integer + prefix: + description: |- + prefix specify the prefix for the given domain. + The default value is the name of the cluster. + minLength: 1 + type: string + servicePorts: + description: servicePorts specify the user-provided service + port(s). + items: + description: ServicePort contains information on service's + port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations of Client + IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - domain + type: object + nodePort: + description: nodePort specifies the configuration to create a + Kubernetes node port service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to `://:, where`podId` starts from `0` to `replicaCount - 1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and value + pairs. It specifies Kubernetes annotations for this service. + type: object + x-kubernetes-map-type: granular + externalTrafficPolicy: + description: |- + externalTrafficPolicy specifies the external traffic policy for the service. + Valid options are `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + host: + description: host defines the host name of the cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value pairs. + It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + nodePortOffset: + description: |- + nodePortOffset specifies the starting offset of the node ports. The port numbers go in ascending order with respect + to the replicas count. + NodePort service creation fails if the node port is not in the range supported by the Kubernetes API server. + The default Kubernetes Node Port range is `30000` - `32762`. + format: int32 + minimum: 0 + type: integer + servicePorts: + description: |- + servicePorts specify user-provided service port(s). + For Kafka with the nodePort type, this setting is only applied to Kafka bootstrap service. + items: + description: ServicePort contains information on service's + port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations of Client + IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - host + - nodePortOffset + type: object + route: + description: route specifies the configuration to create a route + service in OpenShift. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://-http-external.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and value + pairs. It specifies Kubernetes annotations for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain specifies the domain name of the Confluent + component cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value pairs. + It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + prefix: + description: |- + prefix specifies the component prefix when configured for the domain. + The default value is the name of the cluster. + minLength: 1 + type: string + wildcardPolicy: + description: |- + wildcardPolicy allows you to define a route that covers all hosts within a domain. Valid options are `Subdomain` and `None`. + The default value is `None`. + enum: + - Subdomain + - None + type: string + required: + - domain + type: object + type: + description: |- + type specifies the Kubernetes external service for the component. + Valid options are `loadBalancer`, `nodePort`, and `route`. + enum: + - loadBalancer + - nodePort + - route + minLength: 1 + type: string + required: + - type + type: object + headlessService: + description: headlessService specifies the configuration of the Kubernetes + headless service. + properties: + annotations: + additionalProperties: + type: string + description: |- + annotations is a map of string key and value pairs. + It specifies the annotations to be added to the CFK-created headless service. + These annotations are merged with the injectAnnotations and take precedence. + type: object + x-kubernetes-map-type: granular + labels: + additionalProperties: + type: string + description: |- + labels is a map of string key and value pairs. + It specifies the labels to be added to the CFK-created headless service. + These labels are merged with the injectLabels and take precedence. + type: object + x-kubernetes-map-type: granular + publishNotReadyAddresses: + description: |- + publishNotReadyAddresses specifies the publishNotReadyAddresses field. + For Kafka, this value must be true. The default value is true. + type: boolean + type: object + image: + description: |- + image specifies the application and the init docker image configurations. + A change to this setting will roll the cluster. + properties: + application: + description: |- + application is the Docker image name of the application. Specify + `//:`. + pattern: .+:.+ + type: string + init: + description: |- + init is the init-container name. Specify + `//:`. + pattern: .+:.+ + type: string + pullPolicy: + description: |- + pullPolicy is the policy for pulling images. Valid options are `Always`, `Never`, and `IfNotPresent`. + The default value is `IfNotPresent`. + enum: + - Always + - Never + - IfNotPresent + type: string + pullSecretRef: + description: |- + pullSecretRef references the secrets in the same namespace to be used for pulling images. + Image pull secrets are distinct from secrets because secrets + can be mounted in the pod, but image pull secrets are only accessed by `kubelet`. + More info: https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod + items: + type: string + type: array + required: + - application + - init + type: object + injectAnnotations: + additionalProperties: + type: string + description: |- + injectAnnotations are the annotations injected to the internal resources that CFK created. + The internal annotations are preserved and cannot be overridden. + For pod annotations, use `podTemplate.annotations`. + type: object + x-kubernetes-map-type: granular + injectLabels: + additionalProperties: + type: string + description: |- + injectLabels are the labels injected to the internal resources that CFK created. + The internal labels are preserved and cannot be overridden. + For pod labels, use `podTemplate.labels`. + type: object + x-kubernetes-map-type: granular + internalTopicReplicationFactor: + description: internalTopicReplicationFactor specifies the replication + factor for internal topics. + format: int32 + type: integer + k8sClusterDomain: + description: |- + k8sClusterDomain specifies the configuration of the Kubernetes cluster domain. + The default is the `cluster.local` domain. + type: string + license: + description: license specifies the license configuration for the Confluent + Platform component. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + the license key is mounted. More info: + https://docs.confluent.io/operator/current/co-license.html#update-component-level-licenses + minLength: 1 + type: string + globalLicense: + description: globalLicense specifies whether the Confluent Platform + component shares the common global license. + type: boolean + secretRef: + description: |- + secretRef references the secret that provides the license for the Confluent Platform component. + More info: https://docs.confluent.io/operator/current/co-license.html#update-component-level-licenses + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + listeners: + description: listeners specify the listeners configurations. + properties: + external: + description: external specifies the Confluent component external + listener. + properties: + externalAccess: + description: externalAccess defines the external access configuration + for the Confluent component. + properties: + loadBalancer: + description: loadBalancer specifies the configuration + to create a Kubernetes load balancer service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain is the domain name of the component + cluster. + minLength: 1 + type: string + externalTrafficPolicy: + description: externalTrafficPolicy specifies the external + traffic policy for the service. Valid options are + `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + loadBalancerSourceRanges: + description: loadBalancerSourceRanges specify the + source ranges. + items: + type: string + type: array + port: + description: |- + port specifies the external port for the client consumption. + If not configured, the same internal/external port is configured for the component. + Information about the port can be retrieved through the status API. + format: int32 + type: integer + prefix: + description: |- + prefix specify the prefix for the given domain. + The default value is the name of the cluster. + minLength: 1 + type: string + servicePorts: + description: servicePorts specify the user-provided + service port(s). + items: + description: ServicePort contains information on + service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by + this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - domain + type: object + nodePort: + description: nodePort specifies the configuration to create + a Kubernetes node port service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to `://:, where`podId` starts from `0` to `replicaCount - 1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + externalTrafficPolicy: + description: |- + externalTrafficPolicy specifies the external traffic policy for the service. + Valid options are `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + host: + description: host defines the host name of the cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + nodePortOffset: + description: |- + nodePortOffset specifies the starting offset of the node ports. The port numbers go in ascending order with respect + to the replicas count. + NodePort service creation fails if the node port is not in the range supported by the Kubernetes API server. + The default Kubernetes Node Port range is `30000` - `32762`. + format: int32 + minimum: 0 + type: integer + servicePorts: + description: |- + servicePorts specify user-provided service port(s). + For Kafka with the nodePort type, this setting is only applied to Kafka bootstrap service. + items: + description: ServicePort contains information on + service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by + this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - host + - nodePortOffset + type: object + route: + description: route specifies the configuration to create + a route service in OpenShift. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://-http-external.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain specifies the domain name of the + Confluent component cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + prefix: + description: |- + prefix specifies the component prefix when configured for the domain. + The default value is the name of the cluster. + minLength: 1 + type: string + wildcardPolicy: + description: |- + wildcardPolicy allows you to define a route that covers all hosts within a domain. Valid options are `Subdomain` and `None`. + The default value is `None`. + enum: + - Subdomain + - None + type: string + required: + - domain + type: object + type: + description: |- + type specifies the Kubernetes external service for the component. + Valid options are `loadBalancer`, `nodePort`, and `route`. + enum: + - loadBalancer + - nodePort + - route + minLength: 1 + type: string + required: + - type + type: object + tls: + description: tls specifies the TLS configuration for the listener. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + internal: + description: |- + internal specifies the Confluent component's internal listener. + This internal listener is for intra-communication between the pods. + properties: + port: + description: |- + port binds the given port to the internal listener. If not configured, + it will be defaulted to the component-specific internal port. + Port numbers lower than `9093` are reserved by CFK. + format: int32 + minimum: 9093 + type: integer + tls: + description: tls specifies the TLS configuration for the listener. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + type: object + metrics: + description: metrics specify the security settings for the metric + services. + properties: + authentication: + description: authentication specifies the authentication configuration + for the metrics. + properties: + type: + description: type specifies the metrics authentication method. + The valid option is `mtls`. + enum: + - mtls + type: string + required: + - type + type: object + prometheus: + description: prometheus specifies the configuration overrides + for the JMX-Prometheus exporter. + properties: + blacklist: + items: + type: string + type: array + rules: + items: + description: Rule defines the Prometheus Exporter rule override. + properties: + attrNameSnakeCase: + type: boolean + cache: + type: boolean + help: + minLength: 1 + type: string + labels: + additionalProperties: + type: string + type: object + x-kubernetes-map-type: granular + name: + minLength: 1 + type: string + pattern: + minLength: 1 + type: string + type: + minLength: 1 + type: string + value: + minLength: 1 + type: string + valueFactor: + anyOf: + - type: integer + - type: string + default: 1 + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: array + whitelist: + items: + type: string + type: array + type: object + tls: + description: tls specifies the TLS configuration for the metrics. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + mountedSecrets: + description: |- + mountedSecrets list the secrets injected to + the underlying statefulset configuration. The secret reference is mounted + in the default path `/mnt/secrets/`. The underlying resources + will follow the secret as a file configuration. + More info: https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets-as-files-from-a-pod. + A change to this setting will roll the cluster. + items: + description: |- + MountedSecrets provides a way to inject a custom secret to the underlying + statefulset. + properties: + keyItems: + description: keyItems are key and path names. + items: + description: Maps a string key to a path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + secretRef: + description: secretRef references the name of the secret. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + type: array + mountedVolumes: + description: |- + mountedVolumes list the custom volumes that need to be mounted into the + underlying statefulset. + A change to this setting will roll the cluster. + properties: + volumeMounts: + description: |- + volumeMounts specify the list of volume mounts for the pods in the + statefulset. + items: + description: VolumeMount describes a mounting of a Volume within + a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + description: |- + volumes specify the list of volumes that can be mounted into the pods + of statefulset. + items: + description: Volume represents a named volume in a pod that + may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + format: int32 + type: integer + readOnly: + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: boolean + volumeID: + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + required: + - volumeID + type: object + azureDisk: + description: azureDisk represents an Azure Data Disk mount + on the host and bind mount to the pod. + properties: + cachingMode: + description: 'cachingMode is the Host Caching mode: + None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the data disk in + the blob storage + type: string + diskURI: + description: diskURI is the URI of data disk in the + blob storage + type: string + fsType: + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: multiple + blob disks per storage account Dedicated: single + blob disk per storage account Managed: azure managed + data disk (only in managed availability set). defaults + to shared' + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: azureFile represents an Azure File Service + mount on the host and bind mount to the pod. + properties: + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of secret that + contains Azure Storage Account Name and Key + type: string + shareName: + description: shareName is the azure share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: cephFS represents a Ceph FS mount on the host + that shares a pod's lifetime + properties: + monitors: + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + items: + type: string + type: array + path: + description: 'path is Optional: Used as the mounted + root, rather than the full Ceph tree, default is /' + type: string + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: boolean + secretFile: + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + secretRef: + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + required: + - monitors + type: object + cinder: + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: boolean + secretRef: + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + description: csi (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers (Beta feature). + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward API about the + pod that should populate this volume + properties: + defaultMode: + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents information + to create the file containing the pod field + properties: + fieldRef: + description: 'Required: Selects a field of the + pod: only annotations, labels, name and namespace + are supported.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must not + be absolute or contain the ''..'' path. Must + be utf-8 encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + emptyDir: + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + properties: + medium: + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: 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]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. + properties: + volumeClaimTemplate: + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + + Required, must not be nil. + properties: + metadata: + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. + type: object + spec: + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim + are also valid here. + properties: + accessModes: + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + items: + type: string + type: array + dataSource: + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + 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. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any 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. + 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 + required: + - kind + - name + type: object + resources: + description: |- + resources represents the minimum resources the volume should have. + If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + selector: + description: selector is a label query over + volumes to consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + type: string + volumeAttributesClassName: + description: |- + volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. + If specified, the CSI driver will create or update the volume with the attributes defined + in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, + it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass + will be applied to the claim but it's not allowed to reset this field to empty string once it is set. + If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass + will be set by the persistentvolume controller if it exists. + If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be + set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource + exists. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#volumeattributesclass + (Alpha) Using this field requires the VolumeAttributesClass feature gate to be enabled. + type: string + volumeMode: + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding reference + to the PersistentVolume backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource that + is attached to a kubelet's host machine and then exposed + to the pod. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + lun: + description: 'lun is Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC target worldwide + names (WWNs)' + items: + type: string + type: array + wwids: + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. + items: + type: string + type: array + type: object + flexVolume: + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. + properties: + driver: + description: driver is the name of the driver to use + for this volume. + type: string + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this field holds + extra command options if any.' + type: object + readOnly: + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + description: flocker represents a Flocker volume attached + to a kubelet's host machine. This depends on the Flocker + control service being running + properties: + datasetName: + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of the dataset. + This is unique identifier of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + properties: + fsType: + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + format: int32 + type: integer + pdName: + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: boolean + required: + - pdName + type: object + gitRepo: + description: |- + gitRepo represents a git repository at a particular revision. + DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. + properties: + directory: + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash for the specified + revision. + type: string + required: + - repository + type: object + glusterfs: + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/glusterfs/README.md + properties: + endpoints: + description: |- + endpoints is the endpoint name that details Glusterfs topology. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + path: + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + readOnly: + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- + TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not + mount host directories as read/write. + properties: + path: + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + type: + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + required: + - path + type: object + iscsi: + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://examples.k8s.io/volumes/iscsi/README.md + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether support + iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether support + iSCSI Session CHAP authentication + type: boolean + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + initiatorName: + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified Name. + type: string + iscsiInterface: + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret for iSCSI + target and initiator authentication + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + nfs: + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + properties: + path: + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + readOnly: + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: boolean + server: + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + properties: + claimName: + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + type: string + readOnly: + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: photonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets host + machine + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies Photon Controller + persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: portworxVolume represents a portworx volume + attached and mounted on kubelets host machine + properties: + fsType: + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one resources secrets, + configmaps, and downward API + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + sources: + description: sources is the list of volume projections + items: + description: Projection that may be projected along + with other supported volume types + properties: + clusterTrustBundle: + description: |- + ClusterTrustBundle allows a pod to access the `.spec.trustBundle` field + of ClusterTrustBundle objects in an auto-updating file. + + + Alpha, gated by the ClusterTrustBundleProjection feature gate. + + + ClusterTrustBundle objects can either be selected by name, or by the + combination of signer name and a label selector. + + + Kubelet performs aggressive normalization of the PEM contents written + into the pod filesystem. Esoteric PEM features such as inter-block + comments and block headers are stripped. Certificates are deduplicated. + The ordering of certificates within the file is arbitrary, and Kubelet + may change the order over time. + properties: + labelSelector: + description: |- + Select all ClusterTrustBundles that match this label selector. Only has + effect if signerName is set. Mutually-exclusive with name. If unset, + interpreted as "match nothing". If set but empty, interpreted as "match + everything". + 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 + name: + description: |- + Select a single ClusterTrustBundle by object name. Mutually-exclusive + with signerName and labelSelector. + type: string + optional: + description: |- + If true, don't block pod startup if the referenced ClusterTrustBundle(s) + aren't available. If using name, then the named ClusterTrustBundle is + allowed not to exist. If using signerName, then the combination of + signerName and labelSelector is allowed to match zero + ClusterTrustBundles. + type: boolean + path: + description: Relative path from the volume + root to write the bundle. + type: string + signerName: + description: |- + Select all ClusterTrustBundles that match this signer name. + Mutually-exclusive with name. The contents of all selected + ClusterTrustBundles will be unified and deduplicated. + type: string + required: + - path + type: object + configMap: + description: configMap information about the configMap + data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional specify whether the + ConfigMap or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about the + downwardAPI data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a field + of the pod: only annotations, labels, + name and namespace are supported.' + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in terms + of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to + select in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file to + be created. Must not be absolute or + contain the ''..'' path. Must be utf-8 + encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env + vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + secret: + description: secret information about the secret + data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional field specify whether + the Secret or its key must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information + about the serviceAccountToken data to project + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + format: int64 + type: integer + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + description: quobyte represents a Quobyte mount on the host + that shares a pod's lifetime + properties: + group: + description: |- + group to map volume access to + Default is no group + type: string + readOnly: + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes + type: string + tenant: + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: |- + user to map volume access to + Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references an already + created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/rbd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + image: + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + keyring: + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + monitors: + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + items: + type: string + type: array + pool: + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: boolean + secretRef: + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + required: + - image + - monitors + type: object + scaleIO: + description: scaleIO represents a ScaleIO persistent volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". + type: string + gateway: + description: gateway is the host address of the ScaleIO + API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name of the ScaleIO + Protection Domain for the configured storage. + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + description: sslEnabled Flag enable/disable SSL communication + with Gateway, default false + type: boolean + storageMode: + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO Storage Pool + associated with the protection domain. + type: string + system: + description: system is the name of the storage system + as configured in ScaleIO. + type: string + volumeName: + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: optional field specify whether the Secret + or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + type: object + storageos: + description: storageOS represents a StorageOS volume attached + and mounted on Kubernetes nodes. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. + type: string + volumeNamespace: + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: vsphereVolume represents a vSphere volume attached + and mounted on kubelets host machine + properties: + fsType: + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage Policy Based + Management (SPBM) profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage Policy + Based Management (SPBM) profile name. + type: string + volumePath: + description: volumePath is the path that identifies + vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + required: + - volumeMounts + - volumes + type: object + oneReplicaPerNode: + description: |- + oneReplicaPerNode controls whether to run 1 pod per node using the pod anti-affinity capability. + Enabling this configuration in an existing cluster will roll the cluster. + type: boolean + pdb: + description: |- + configures PodDisruptionBudget for the Confluent Platform component. + by default PDB is configured based on pre-detemined formula. + properties: + enabled: + description: enabled specifies whether the PodDisruptionBudget + is enabled + type: boolean + maxUnavailable: + description: maxUnavailable is the maximum number of pods that + can be unavailable during the disruption. + format: int32 + type: integer + required: + - enabled + type: object + podTemplate: + description: podTemplate specifies the statefulset pod template configuration. + properties: + affinity: + description: |- + affinity specifies a group of affinity scheduling rules. + More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + type: array + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + annotations: + additionalProperties: + type: string + description: |- + annotations is a map of string key and value pairs stored with the resource and + may be set by external tools to store and retrieve arbitrary metadata. They + are not queryable and should be preserved when modifying objects. More + info: http://kubernetes.io/docs/user-guide/annotations. + type: object + x-kubernetes-map-type: granular + envVars: + description: |- + envVars contain environment variables to be injected into containers. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container. + 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 + labels: + additionalProperties: + type: string + description: |- + labels is a map of string key and value pairs that can be used to organize and categorize + (scope and select) objects. + More info: http://kubernetes.io/docs/user-guide/labels. + type: object + x-kubernetes-map-type: granular + podSecurityContext: + description: |- + PodSecurityContext holds pod-level security attributes and common container settings. + Some fields are also present in container.securityContext. Field values of + container.securityContext take precedence over field values of PodSecurityContext. + 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: + + + 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---- + + + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + 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 + type: object + 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. + 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: + + + 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 + required: + - type + type: object + 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. + items: + format: int64 + type: integer + type: array + 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. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + 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. + 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 + type: object + type: object + priorityClassName: + description: priorityClassName specifies the priority class for + the pod (if any). + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + probe: + description: probe contains the fields for standard Kubernetes + readiness/liveness probe configuration. + properties: + liveness: + description: |- + liveness configures the Kubernetes probe settings. The changes + will override the existing default configuration. + properties: + failureThreshold: + description: |- + failureThreshold is the minimum consecutive failures for the probe to be considered failed. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + initialDelaySeconds: + description: |- + initialDelaySeconds is the number of seconds after the container has started and before probes are initiated. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + path: + description: Path for the HTTP probe + type: string + periodSeconds: + description: |- + periodSeconds specifies how often to perform the probe. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + port: + description: Number of the port to access on the container + type: integer + successThreshold: + description: |- + successThreshold is the minimum consecutive successes for the probe to be considered successful after having failed. + The default values is `1`. Must be `1` for liveness and startup. The minimum value is `1`. + format: int32 + type: integer + timeoutSeconds: + description: |- + timeoutSeconds is the number of seconds after which the probe times out. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + type: object + readiness: + description: |- + readiness configures the Kubernetes probe setting. The changes + will override the existing default configuration. + properties: + failureThreshold: + description: |- + failureThreshold is the minimum consecutive failures for the probe to be considered failed. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + initialDelaySeconds: + description: |- + initialDelaySeconds is the number of seconds after the container has started and before probes are initiated. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + path: + description: Path for the HTTP probe + type: string + periodSeconds: + description: |- + periodSeconds specifies how often to perform the probe. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + port: + description: Number of the port to access on the container + type: integer + successThreshold: + description: |- + successThreshold is the minimum consecutive successes for the probe to be considered successful after having failed. + The default values is `1`. Must be `1` for liveness and startup. The minimum value is `1`. + format: int32 + type: integer + timeoutSeconds: + description: |- + timeoutSeconds is the number of seconds after which the probe times out. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + type: object + type: object + resources: + description: resources describe 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. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: |- + SecurityContext holds security configuration that will be applied to a container. + Some fields are present in both SecurityContext and PodSecurityContext. When both + are set, the values in SecurityContext take precedence. + 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. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + 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 + type: object + 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. + 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: + + + 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 + required: + - type + type: object + 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. + 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 + type: object + type: object + serviceAccountName: + description: |- + ServiceAccountName is the name of the service account used to run this pod. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account. + type: string + terminationGracePeriodSeconds: + description: terminationGracePeriodSeconds is the grace period + before the pod is deleted. + format: int64 + type: integer + tolerations: + description: |- + tolerations specify the pods to schedule onto the nodes with matching taints, using + the triple `` and the matching operator ``. + 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 + topologySpreadConstraints: + description: |- + topologySpreadConstraints describe how a group of pods ought to spread across topology domains. Scheduler will + schedule pods based on the constraints. All topologySpreadConstraints are ANDed. + items: + description: TopologySpreadConstraint specifies how to spread + matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + 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. + + + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + 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. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + + + This is a beta field and requires the MinDomainsInPodTopologySpread feature gate to be enabled (enabled by default). + format: int32 + type: integer + 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. + + + 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. + + + 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 + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + replicas: + description: |- + replicas is the desired number of replicas. + A change to this setting will roll the cluster. + format: int32 + type: integer + storageClass: + description: storageClass specifies the storage class used for creating + the PVC for the ksqlDB cluster. + properties: + name: + description: name is the storage class name. + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + telemetry: + description: telemetry specifies the Confluent telemetry reporter + configuration. + properties: + global: + description: |- + global allows disabling telemetry configuration. + If CFK is deployed with telemetry, this field is only + used to disable telemetry. The default value is `true` if + telemetry is enabled at the global level. + type: boolean + type: object + tls: + description: tls specifies the global TLS configurations for the ksqlDB + cluster. + properties: + autoGeneratedCerts: + description: |- + autoGeneratedCerts specifies that the certificates are auto-generated based on + the CA key pair provided. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + fips: + description: |- + fips specifies the configuration of FIPS compliant Bouncy Castle type Java Keystores for the cp component's + TLS settings. TLS Secrets must have the keys keystore.bcfks, truststore.bcfks, and jksPassword.txt + properties: + enabled: + description: enabled specifies whether to enable the FIPS + configuration for cp components. + type: boolean + required: + - enabled + type: object + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing the + JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + required: + - dataVolumeCapacity + - image + type: object + status: + description: status defines the observed state of ksqlDB Server. + properties: + arbitraryData: + description: arbitraryData is the map for any arbitrary data associated + with this Confluent component. + x-kubernetes-preserve-unknown-fields: true + authorizationType: + description: authorizationType is the authorization type for this + Confluent component. + type: string + clusterName: + description: clusterName is the name of the Confluent Platform component + cluster. + type: string + clusterNamespace: + description: clusterNamespace is the namespace where the Confluent + Platform component cluster is running. + type: string + conditions: + description: conditions specify the latest available observations + of the current state. + items: + description: Condition represent the latest available observations + of the current state. + properties: + lastProbeTime: + description: lastProbeTime shows the last time the condition + was evaluated. + format: date-time + type: string + lastTransitionTime: + description: lastTransitionTime shows the last time the condition + was transitioned from one status to another. + format: date-time + type: string + message: + description: message shows a human-readable message with details + about the transition. + type: string + reason: + description: reason shows the reason for the last transition + of the condition. + type: string + status: + description: status shows the status of the condition, one of + `True`, `False`, or `Unknown`. + type: string + type: + description: type shows the condition type. + type: string + type: object + type: array + currentReplicas: + description: currentReplicas is the number of currently running replicas. + format: int32 + type: integer + internalSecrets: + description: |- + internalSecrets are internal secrets created + by CFK for this Confluent component. + items: + type: string + type: array + internalTopicNames: + description: internalTopicNames are the topics used by the component + for internal use. + items: + type: string + type: array + kafka: + description: kafka is the Kafka client side status for the ksqlDB + cluster. + properties: + authenticationType: + description: authenticationType describes the authentication method + for the Kafka cluster. + type: string + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap endpoint. + type: string + tls: + description: tls indicates whether TLS is enabled for the Kafka + dependency. + type: boolean + type: object + listeners: + additionalProperties: + description: ListenerStatus describes general information about + the listeners. + properties: + advertisedExternalEndpoints: + description: advertisedExternalEndpoints specifies other advertised + endpoints used, especially for Kafka. + items: + type: string + type: array + authenticationType: + description: authenticationType shows the authentication type + configured by the listener. + type: string + externalAccessType: + description: externalAccessType shows the external access type + used for the listener. + type: string + externalEndpoint: + description: externalEndpoint specifies the external endpoint + to connect to the Confluent component cluster. + type: string + internalEndpoint: + description: internalEndpoint specifies the internal endpoint + to connect to the Confluent component cluster. + type: string + tls: + description: tls shows whether TLS is configured for the listener. + type: boolean + type: object + description: listeners is a map of listener type and the status of + KsqlDB Listeners. + type: object + x-kubernetes-map-type: granular + observedGeneration: + description: observedGeneration is the most recent generation observed + for this Confluent component. + format: int64 + type: integer + operatorVersion: + description: operatorVersion is the internal version of CFK. + type: string + phase: + description: |- + phase describes the state of the Confluent Platform component. This can either be 'PROVISIONING' + or 'RUNNING' + 'PROVISIONING' means the Confluent Platform component is currently getting deployed and not ready yet. + 'RUNNING' means the Confluent Platform component has been successfully deployed. + type: string + rbac: + description: rbac contains the RBAC-related status when RBAC is enabled. + properties: + clusterID: + description: clusterID specifies the id of the cluster. + type: string + internalRolebindings: + description: internalRolebindings specifies the internal rolebindings. + items: + type: string + type: array + type: object + readyReplicas: + description: readyReplicas is the number of currently ready replicas. + format: int32 + type: integer + replicas: + description: replicas is the number of replicas. + format: int32 + type: integer + restConfig: + description: restConfig is the REST API configuration of the ksqlDB + cluster. + properties: + advertisedExternalEndpoints: + description: advertisedExternalEndpoints specifies other advertised + endpoints used, especially for Kafka. + items: + type: string + type: array + authenticationType: + description: authenticationType shows the authentication type + configured by the listener. + type: string + externalAccessType: + description: externalAccessType shows the external access type + used for the listener. + type: string + externalEndpoint: + description: externalEndpoint specifies the external endpoint + to connect to the Confluent component cluster. + type: string + internalEndpoint: + description: internalEndpoint specifies the internal endpoint + to connect to the Confluent component cluster. + type: string + tls: + description: tls shows whether TLS is configured for the listener. + type: boolean + type: object + selector: + description: |- + selector gets the label selector of the child pod. + The Horizontal Pod Autoscaler(HPA) will scale using the label selector of the child pod. + type: string + serviceID: + description: serviceID is the id of the ksqlDB service. + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + scale: + labelSelectorPath: .status.selector + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + status: {} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_schemaexporters.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_schemaexporters.yaml new file mode 100644 index 000000000..fd23b7028 --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_schemaexporters.yaml @@ -0,0 +1,688 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: schemaexporters.platform.confluent.io +spec: + group: platform.confluent.io + names: + categories: + - all + - confluent-platform + - confluent + kind: SchemaExporter + listKind: SchemaExporterList + plural: schemaexporters + shortNames: + - se + - schemaexporter + singular: schemaexporter + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.contextName + name: ContextName + type: string + - jsonPath: .status.exporterStatus + name: ExporterStatus + type: string + - jsonPath: .status.state + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.sourceSchemaRegistry.endpoint + name: SourceSchemaRegistryEndpoint + priority: 1 + type: string + - jsonPath: .status.destinationSchemaRegistry.endpoint + name: DestinationSchemaRegistryEndpoint + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: SchemaExporter is the schema for the SchemaExporter 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: spec defines the desired state of the schema exporter. + properties: + configs: + additionalProperties: + type: string + description: |- + configs is a map of string key and value pairs. It specifies additional configurations for the schema exporter. More info: + https://docs.confluent.io/platform/current/schema-registry/schema-linking-cp.html#create-a-configuration-file-for-the-exporter + type: object + x-kubernetes-map-type: granular + contextName: + description: |- + contextName specifies the custom context name in the destination Schema Registry cluster where the + schemas will be exported. If this is defined, contextType will be ignored. If this is not defined, + schemas will be exported to context in destination based on contextType. + type: string + contextType: + description: |- + contextType specifies the type of context created in the destination Schema Registry cluster of + the schema exporter. + Valid options are `AUTO` and `NONE`. + The default value is `AUTO`. + enum: + - AUTO + - NONE + type: string + destinationCluster: + description: |- + destinationCluster specifies the destination Schema Registry cluster. If this is not defined, + sourceCluster is chosen as the destination and the schema exporter will be exporting + schemas across contexts within the sourceCluster. + Schema exporter should be enabled in Schema Registry cluster CR with `spec.enableSchemaExporter`. + properties: + schemaRegistryClusterRef: + description: schemaRegistryClusterRef references the CFK-managed + Schema Registry cluster. + properties: + name: + description: name specifies the name of the Confluent Platform + component cluster. + type: string + namespace: + description: namespace specifies the namespace where the Confluent + Platform component cluster is running. + type: string + required: + - name + type: object + schemaRegistryRest: + description: schemaRegistryRest specifies the Schema Registry + REST API configuration. + properties: + authentication: + description: authentication specifies the REST API authentication + mechanism. + properties: + basic: + description: basic specifies the basic authentication + settings for the REST API client. + properties: + debug: + description: debug enables the basic authentication + debug logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + bearer: + description: bearer specifies the bearer authentication + settings for the REST API client. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: oauth specifies the OAuth authentication + settings for the REST API client. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass + the basic credential through a directory path in + the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to + pass the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the REST API authentication + type. Valid options are `basic`, `bearer`, `mtls` and + `oauth`. + enum: + - basic + - bearer + - mtls + - oauth + type: string + required: + - type + type: object + endpoint: + description: endpoint specifies where Confluent REST API is + running. + minLength: 1 + pattern: ^https?://.* + type: string + kafkaClusterID: + description: |- + kafkaClusterID specifies the id of Kafka cluster. + It takes precedence over using the Kafka REST API to get the cluster id. + minLength: 1 + type: string + tls: + description: "tls specifies the custom TLS structure for the + application resources,\n\t// e.g. connector, topic, schema, + of the Confluent Platform components.\n\t// +optional" + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer contains the directory path in the container where + `keystore.jks`, `truststore.jks`, `jksPassword.txt` keys are mounted. + minLength: 1 + type: string + jksPassword: + description: jksPassword specifies the secret name that + contains the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef specifies the secret name that contains the certificates. + More info about certificates key/value format: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: object + type: object + sourceCluster: + description: |- + sourceCluster specifies the source Schema Registry cluster. Schema exporter will be set + up in the source cluster. If this is not defined, controller will try to auto discover Schema Registry + in the namespace of the schema exporter. If it cannot discover a Schema Registry cluster or more than + one Schema Registry clusters are found, controller will return error. + Schema exporter should be enabled in Schema Registry cluster CR with `spec.enableSchemaExporter`. + properties: + schemaRegistryClusterRef: + description: schemaRegistryClusterRef references the CFK-managed + Schema Registry cluster. + properties: + name: + description: name specifies the name of the Confluent Platform + component cluster. + type: string + namespace: + description: namespace specifies the namespace where the Confluent + Platform component cluster is running. + type: string + required: + - name + type: object + schemaRegistryRest: + description: schemaRegistryRest specifies the Schema Registry + REST API configuration. + properties: + authentication: + description: authentication specifies the REST API authentication + mechanism. + properties: + basic: + description: basic specifies the basic authentication + settings for the REST API client. + properties: + debug: + description: debug enables the basic authentication + debug logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + bearer: + description: bearer specifies the bearer authentication + settings for the REST API client. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: oauth specifies the OAuth authentication + settings for the REST API client. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass + the basic credential through a directory path in + the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to + pass the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the REST API authentication + type. Valid options are `basic`, `bearer`, `mtls` and + `oauth`. + enum: + - basic + - bearer + - mtls + - oauth + type: string + required: + - type + type: object + endpoint: + description: endpoint specifies where Confluent REST API is + running. + minLength: 1 + pattern: ^https?://.* + type: string + kafkaClusterID: + description: |- + kafkaClusterID specifies the id of Kafka cluster. + It takes precedence over using the Kafka REST API to get the cluster id. + minLength: 1 + type: string + tls: + description: "tls specifies the custom TLS structure for the + application resources,\n\t// e.g. connector, topic, schema, + of the Confluent Platform components.\n\t// +optional" + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer contains the directory path in the container where + `keystore.jks`, `truststore.jks`, `jksPassword.txt` keys are mounted. + minLength: 1 + type: string + jksPassword: + description: jksPassword specifies the secret name that + contains the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef specifies the secret name that contains the certificates. + More info about certificates key/value format: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: object + type: object + subjectRenameFormat: + description: |- + subjectRenameFormat specifies the rename format for the subjects exported to the destination. + For example, if the value is `my-${subject}`, subjects at destination will become `my-firstSubject` + where `firstSubject` is the original subject name. + type: string + subjects: + description: |- + subjects specifies the list of subjects to be exported by schema exporter. + The default value is `["*"]`. This indicates all subjects in the default context. + items: + type: string + type: array + type: object + status: + description: status defines the observed state of the schema exporter. + properties: + appState: + default: Unknown + description: appState is the current state of the schema exporter + application. + enum: + - Unknown + - Created + - Failed + - Deleted + type: string + conditions: + description: conditions are the latest available observations of the + schema exporter's state. + items: + description: Condition represent the latest available observations + of the current state. + properties: + lastProbeTime: + description: lastProbeTime shows the last time the condition + was evaluated. + format: date-time + type: string + lastTransitionTime: + description: lastTransitionTime shows the last time the condition + was transitioned from one status to another. + format: date-time + type: string + message: + description: message shows a human-readable message with details + about the transition. + type: string + reason: + description: reason shows the reason for the last transition + of the condition. + type: string + status: + description: status shows the status of the condition, one of + `True`, `False`, or `Unknown`. + type: string + type: + description: type shows the condition type. + type: string + type: object + type: array + contextName: + description: |- + contextName shows the name of the context in the destination Schema Registry cluster + where the schemas will be exported. + type: string + contextType: + description: contextType is the contextType of the schema exporter. + type: string + destinationSchemaRegistry: + description: |- + destinationSchemaRegistry shows the destination Schema Registry endpoint, authentication type + and if it is using TLS. + properties: + authenticationType: + description: authenticationType is the authentication method used + for Schema Registry. + type: string + endpoint: + description: endpoint is the Schema Registry REST endpoint. + type: string + tls: + description: tls shows whether the Schema Registry is using TLS. + type: boolean + type: object + exporterStatus: + description: exporterStatus is the status of the schema exporter. + type: string + observedGeneration: + description: observedGeneration is the most recent generation observed + for this Confluent component. + format: int64 + type: integer + sourceSchemaRegistry: + description: |- + sourceSchemaRegistry shows the source Schema Registry endpoint, authentication type + and if it is using TLS. + properties: + authenticationType: + description: authenticationType is the authentication method used + for Schema Registry. + type: string + endpoint: + description: endpoint is the Schema Registry REST endpoint. + type: string + tls: + description: tls shows whether the Schema Registry is using TLS. + type: boolean + type: object + state: + description: state is the current state of the schema exporter. + type: string + subjects: + description: subjects is the list of subjects exported by the schema + exporter. + items: + type: string + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_schemaregistries.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_schemaregistries.yaml new file mode 100644 index 000000000..9d0b7ac08 --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_schemaregistries.yaml @@ -0,0 +1,5801 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: schemaregistries.platform.confluent.io +spec: + group: platform.confluent.io + names: + categories: + - all + - confluent-platform + - confluent + kind: SchemaRegistry + listKind: SchemaRegistryList + plural: schemaregistries + shortNames: + - schemaregistry + - sr + singular: schemaregistry + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.replicas + name: Replicas + type: string + - jsonPath: .status.readyReplicas + name: Ready + type: string + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.kafka.bootstrapEndpoint + name: Kafka + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: SchemaRegistry is the schema for the Schema Registry 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: spec defines the desired state of the Schema Registry cluster. + properties: + authentication: + description: authentication specifies the authentication configurations + for the REST API endpoint. + properties: + basic: + description: basic specifies the configuration for basic authentication. + properties: + debug: + description: debug enables the basic authentication debug + logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: OAuth specifies the configuration for OAuth authentication. + properties: + configuration: + description: configuration specifies the OAuth server settings. + properties: + audience: + description: audience specifies the audience claim in + the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected issuer + in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of claim + in token for identifying the groups of subject in the + JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout with + IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry backoff + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff with + IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim in JWT + to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass the basic + credential through a directory path in the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to pass the + required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication scheme for the + REST API server. Valid options are `basic` and `mtls`. + enum: + - basic + - mtls + - oauth + type: string + required: + - type + type: object + authorization: + description: authorization specifies the authorization configurations. + properties: + kafkaRestClassRef: + description: |- + kafkaRestClassRef references the KafkaRestClass + which specifies the Kafka REST API connection configuration. + properties: + name: + description: name specifies the name of the KafkaRestClass + application resource. + minLength: 1 + type: string + namespace: + description: namespace specifies the namespace of the KafkaRestClass. + type: string + required: + - name + type: object + type: + description: type specifies the client-side authorization type. + The valid option is `rbac`. + enum: + - rbac + type: string + required: + - type + type: object + configOverrides: + description: |- + configOverrides specifies the configs to override the server, JVM, Log4j properties for the Schema Registry cluster. + A change will roll the cluster. + properties: + jvm: + description: |- + jvm is a list of JVM configuration supported by the Confluent Platform component. + This will either add or update the existing configuration. + items: + type: string + type: array + log4j: + description: |- + log4j is a list of Log4J configuration supported by the Confluent Platform component. + This will either add or update the existing configuration. + items: + type: string + type: array + server: + description: |- + server is a list of server configuration supported by the Confluent Platform component. + This will either add or update existing configuration. + items: + type: string + type: array + type: object + dependencies: + description: dependencies specify the dependency configurations for + the Schema Registry. + properties: + kafka: + description: kafka specifies the Kafka dependency configuration. + properties: + authentication: + description: authentication defines the authentication for + the Kafka cluster. + properties: + jaasConfig: + description: jaasConfig specifies the Kafka client-side + JaaS configuration. + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: jaasConfigPassThrough specifies another way + to provide the Kafka client-side JaaS configuration. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + oauthbearer: + description: |- + oauthbearer is the authentication mechanism to provider principals. + Only supported in RBAC deployment. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: + description: |- + type specifies the Kafka client authentication type. + Valid options are `plain`, `oauthbearer`, `digest`, `mtls` and `oauth`. + enum: + - plain + - oauthbearer + - digest + - mtls + - oauth + type: string + required: + - type + type: object + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap + endpoint. + minLength: 1 + pattern: .+:[0-9]+ + type: string + discovery: + description: discovery specifies the capability to discover + the Kafka cluster. + properties: + name: + description: name is the name of the Confluent Platform + component cluster. + type: string + namespace: + description: |- + namespace is where the Confluent Platform component is running. + The default value is the namespace where CFK is running. + type: string + secretRef: + description: secretRef is the name of the secret used + to discover the Confluent Platform component. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + tls: + description: tls defines the client-side TLS setting for the + Kafka cluster. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + mds: + description: mds specifies the MDS dependencies configurations. + properties: + authentication: + description: authentication specifies the client side authentication + configuration for the MDS. + properties: + bearer: + description: bearer specifies the bearer authentication + settings. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: oauth specifies the OAuth authentication + settings. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name + of claim in token for identifying the groups + of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect + timeout with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass + the basic credential through a directory path in + the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to + pass the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the authentication method + for the MDS. The valid option is `bearer`, `oauth`. + enum: + - bearer + - oauth + type: string + required: + - type + type: object + endpoint: + description: endpoint specifies the MDS endpoint. + minLength: 1 + pattern: ^https?://.* + type: string + ssoProtocol: + description: sso protocol, valid options are ldap and oidc. + enum: + - ldap + - oidc + type: string + tls: + description: ClientTLSConfig specifies the TLS configuration + for the Confluent component (dependencies, listeners). + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + tokenKeyPair: + description: tokenKeyPair specifies the token keypair to configure + the MDS. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer defines the directory path in the container + where the MDS token key pair are mounted. + minLength: 1 + type: string + encryptedTokenKey: + description: |- + EncryptedTokenKey boolean value indicating whether the tokenKeypair(private used for signing) is encrypted using a passphrase. If true, cfk + operator will look for a file named mdsTokenKeyPassphrase.txt containing key value pair + mdsTokenKeyPassphrase=. Relevant only for mds server. Ignored if set for a client configuration. + type: boolean + secretRef: + description: secretRef references the name of the secret + that contains the MDS token key pair. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + required: + - authentication + - endpoint + - tokenKeyPair + type: object + type: object + enableSchemaExporter: + description: enableSchemaExporter enables schema exporter in the Schema + Registry. + type: boolean + externalAccess: + description: |- + externalAccess specifies the external access configuration. + When `spec.listeners` is configured, configuring `spec.externalAccess` is not allowed. + Please configure `spec.listeners.external.externalAccess` instead". + properties: + loadBalancer: + description: loadBalancer specifies the configuration to create + a Kubernetes load balancer service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and value + pairs. It specifies Kubernetes annotations for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain is the domain name of the component cluster. + minLength: 1 + type: string + externalTrafficPolicy: + description: externalTrafficPolicy specifies the external + traffic policy for the service. Valid options are `Local` + and `Cluster`. + enum: + - Local + - Cluster + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value pairs. + It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + loadBalancerSourceRanges: + description: loadBalancerSourceRanges specify the source ranges. + items: + type: string + type: array + port: + description: |- + port specifies the external port for the client consumption. + If not configured, the same internal/external port is configured for the component. + Information about the port can be retrieved through the status API. + format: int32 + type: integer + prefix: + description: |- + prefix specify the prefix for the given domain. + The default value is the name of the cluster. + minLength: 1 + type: string + servicePorts: + description: servicePorts specify the user-provided service + port(s). + items: + description: ServicePort contains information on service's + port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations of Client + IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - domain + type: object + nodePort: + description: nodePort specifies the configuration to create a + Kubernetes node port service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to `://:, where`podId` starts from `0` to `replicaCount - 1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and value + pairs. It specifies Kubernetes annotations for this service. + type: object + x-kubernetes-map-type: granular + externalTrafficPolicy: + description: |- + externalTrafficPolicy specifies the external traffic policy for the service. + Valid options are `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + host: + description: host defines the host name of the cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value pairs. + It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + nodePortOffset: + description: |- + nodePortOffset specifies the starting offset of the node ports. The port numbers go in ascending order with respect + to the replicas count. + NodePort service creation fails if the node port is not in the range supported by the Kubernetes API server. + The default Kubernetes Node Port range is `30000` - `32762`. + format: int32 + minimum: 0 + type: integer + servicePorts: + description: |- + servicePorts specify user-provided service port(s). + For Kafka with the nodePort type, this setting is only applied to Kafka bootstrap service. + items: + description: ServicePort contains information on service's + port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations of Client + IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - host + - nodePortOffset + type: object + route: + description: route specifies the configuration to create a route + service in OpenShift. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://-http-external.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and value + pairs. It specifies Kubernetes annotations for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain specifies the domain name of the Confluent + component cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value pairs. + It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + prefix: + description: |- + prefix specifies the component prefix when configured for the domain. + The default value is the name of the cluster. + minLength: 1 + type: string + wildcardPolicy: + description: |- + wildcardPolicy allows you to define a route that covers all hosts within a domain. Valid options are `Subdomain` and `None`. + The default value is `None`. + enum: + - Subdomain + - None + type: string + required: + - domain + type: object + type: + description: |- + type specifies the Kubernetes external service for the component. + Valid options are `loadBalancer`, `nodePort`, and `route`. + enum: + - loadBalancer + - nodePort + - route + minLength: 1 + type: string + required: + - type + type: object + headlessService: + description: headlessService specifies the configuration of the Kubernetes + headless service. + properties: + annotations: + additionalProperties: + type: string + description: |- + annotations is a map of string key and value pairs. + It specifies the annotations to be added to the CFK-created headless service. + These annotations are merged with the injectAnnotations and take precedence. + type: object + x-kubernetes-map-type: granular + labels: + additionalProperties: + type: string + description: |- + labels is a map of string key and value pairs. + It specifies the labels to be added to the CFK-created headless service. + These labels are merged with the injectLabels and take precedence. + type: object + x-kubernetes-map-type: granular + publishNotReadyAddresses: + description: |- + publishNotReadyAddresses specifies the publishNotReadyAddresses field. + For Kafka, this value must be true. The default value is true. + type: boolean + type: object + image: + description: |- + image specifies the application and the init docker image configurations. + A change to this setting will roll the cluster. + properties: + application: + description: |- + application is the Docker image name of the application. Specify + `//:`. + pattern: .+:.+ + type: string + init: + description: |- + init is the init-container name. Specify + `//:`. + pattern: .+:.+ + type: string + pullPolicy: + description: |- + pullPolicy is the policy for pulling images. Valid options are `Always`, `Never`, and `IfNotPresent`. + The default value is `IfNotPresent`. + enum: + - Always + - Never + - IfNotPresent + type: string + pullSecretRef: + description: |- + pullSecretRef references the secrets in the same namespace to be used for pulling images. + Image pull secrets are distinct from secrets because secrets + can be mounted in the pod, but image pull secrets are only accessed by `kubelet`. + More info: https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod + items: + type: string + type: array + required: + - application + - init + type: object + injectAnnotations: + additionalProperties: + type: string + description: |- + injectAnnotations are the annotations injected to the internal resources that CFK created. + The internal annotations are preserved and cannot be overridden. + For pod annotations, use `podTemplate.annotations`. + type: object + x-kubernetes-map-type: granular + injectLabels: + additionalProperties: + type: string + description: |- + injectLabels are the labels injected to the internal resources that CFK created. + The internal labels are preserved and cannot be overridden. + For pod labels, use `podTemplate.labels`. + type: object + x-kubernetes-map-type: granular + internalTopicReplicatorFactor: + description: internalTopicReplicatorFactor specifies the replication + factor for internal topics. + format: int32 + minimum: 1 + type: integer + k8sClusterDomain: + description: |- + k8sClusterDomain specifies the configuration of the Kubernetes cluster domain. + The default is the `cluster.local` domain. + type: string + license: + description: license specifies the license configuration for the Confluent + Platform component. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + the license key is mounted. More info: + https://docs.confluent.io/operator/current/co-license.html#update-component-level-licenses + minLength: 1 + type: string + globalLicense: + description: globalLicense specifies whether the Confluent Platform + component shares the common global license. + type: boolean + secretRef: + description: |- + secretRef references the secret that provides the license for the Confluent Platform component. + More info: https://docs.confluent.io/operator/current/co-license.html#update-component-level-licenses + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + listeners: + description: listeners specify the listeners configurations. + properties: + external: + description: external specifies the Confluent component external + listener. + properties: + externalAccess: + description: externalAccess defines the external access configuration + for the Confluent component. + properties: + loadBalancer: + description: loadBalancer specifies the configuration + to create a Kubernetes load balancer service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain is the domain name of the component + cluster. + minLength: 1 + type: string + externalTrafficPolicy: + description: externalTrafficPolicy specifies the external + traffic policy for the service. Valid options are + `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + loadBalancerSourceRanges: + description: loadBalancerSourceRanges specify the + source ranges. + items: + type: string + type: array + port: + description: |- + port specifies the external port for the client consumption. + If not configured, the same internal/external port is configured for the component. + Information about the port can be retrieved through the status API. + format: int32 + type: integer + prefix: + description: |- + prefix specify the prefix for the given domain. + The default value is the name of the cluster. + minLength: 1 + type: string + servicePorts: + description: servicePorts specify the user-provided + service port(s). + items: + description: ServicePort contains information on + service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by + this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - domain + type: object + nodePort: + description: nodePort specifies the configuration to create + a Kubernetes node port service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to `://:, where`podId` starts from `0` to `replicaCount - 1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + externalTrafficPolicy: + description: |- + externalTrafficPolicy specifies the external traffic policy for the service. + Valid options are `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + host: + description: host defines the host name of the cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + nodePortOffset: + description: |- + nodePortOffset specifies the starting offset of the node ports. The port numbers go in ascending order with respect + to the replicas count. + NodePort service creation fails if the node port is not in the range supported by the Kubernetes API server. + The default Kubernetes Node Port range is `30000` - `32762`. + format: int32 + minimum: 0 + type: integer + servicePorts: + description: |- + servicePorts specify user-provided service port(s). + For Kafka with the nodePort type, this setting is only applied to Kafka bootstrap service. + items: + description: ServicePort contains information on + service's port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by + this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - host + - nodePortOffset + type: object + route: + description: route specifies the configuration to create + a route service in OpenShift. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://-http-external.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and + value pairs. It specifies Kubernetes annotations + for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain specifies the domain name of the + Confluent component cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value + pairs. It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + prefix: + description: |- + prefix specifies the component prefix when configured for the domain. + The default value is the name of the cluster. + minLength: 1 + type: string + wildcardPolicy: + description: |- + wildcardPolicy allows you to define a route that covers all hosts within a domain. Valid options are `Subdomain` and `None`. + The default value is `None`. + enum: + - Subdomain + - None + type: string + required: + - domain + type: object + type: + description: |- + type specifies the Kubernetes external service for the component. + Valid options are `loadBalancer`, `nodePort`, and `route`. + enum: + - loadBalancer + - nodePort + - route + minLength: 1 + type: string + required: + - type + type: object + tls: + description: tls specifies the TLS configuration for the listener. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + internal: + description: |- + internal specifies the Confluent component's internal listener. + This internal listener is for intra-communication between the pods. + properties: + port: + description: |- + port binds the given port to the internal listener. If not configured, + it will be defaulted to the component-specific internal port. + Port numbers lower than `9093` are reserved by CFK. + format: int32 + minimum: 9093 + type: integer + tls: + description: tls specifies the TLS configuration for the listener. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + type: object + metrics: + description: metrics specify the security settings for the metric + services. + properties: + authentication: + description: authentication specifies the authentication configuration + for the metrics. + properties: + type: + description: type specifies the metrics authentication method. + The valid option is `mtls`. + enum: + - mtls + type: string + required: + - type + type: object + prometheus: + description: prometheus specifies the configuration overrides + for the JMX-Prometheus exporter. + properties: + blacklist: + items: + type: string + type: array + rules: + items: + description: Rule defines the Prometheus Exporter rule override. + properties: + attrNameSnakeCase: + type: boolean + cache: + type: boolean + help: + minLength: 1 + type: string + labels: + additionalProperties: + type: string + type: object + x-kubernetes-map-type: granular + name: + minLength: 1 + type: string + pattern: + minLength: 1 + type: string + type: + minLength: 1 + type: string + value: + minLength: 1 + type: string + valueFactor: + anyOf: + - type: integer + - type: string + default: 1 + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: array + whitelist: + items: + type: string + type: array + type: object + tls: + description: tls specifies the TLS configuration for the metrics. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + mountedSecrets: + description: |- + mountedSecrets list the secrets injected to + the underlying statefulset configuration. The secret reference is mounted + in the default path `/mnt/secrets/`. The underlying resources + will follow the secret as a file configuration. + More info: https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets-as-files-from-a-pod. + A change to this setting will roll the cluster. + items: + description: |- + MountedSecrets provides a way to inject a custom secret to the underlying + statefulset. + properties: + keyItems: + description: keyItems are key and path names. + items: + description: Maps a string key to a path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + secretRef: + description: secretRef references the name of the secret. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + type: array + mountedVolumes: + description: |- + mountedVolumes list the custom volumes that need to be mounted into the + underlying statefulset. + A change to this setting will roll the cluster. + properties: + volumeMounts: + description: |- + volumeMounts specify the list of volume mounts for the pods in the + statefulset. + items: + description: VolumeMount describes a mounting of a Volume within + a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + description: |- + volumes specify the list of volumes that can be mounted into the pods + of statefulset. + items: + description: Volume represents a named volume in a pod that + may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + format: int32 + type: integer + readOnly: + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: boolean + volumeID: + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + required: + - volumeID + type: object + azureDisk: + description: azureDisk represents an Azure Data Disk mount + on the host and bind mount to the pod. + properties: + cachingMode: + description: 'cachingMode is the Host Caching mode: + None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the data disk in + the blob storage + type: string + diskURI: + description: diskURI is the URI of data disk in the + blob storage + type: string + fsType: + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: multiple + blob disks per storage account Dedicated: single + blob disk per storage account Managed: azure managed + data disk (only in managed availability set). defaults + to shared' + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: azureFile represents an Azure File Service + mount on the host and bind mount to the pod. + properties: + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of secret that + contains Azure Storage Account Name and Key + type: string + shareName: + description: shareName is the azure share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: cephFS represents a Ceph FS mount on the host + that shares a pod's lifetime + properties: + monitors: + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + items: + type: string + type: array + path: + description: 'path is Optional: Used as the mounted + root, rather than the full Ceph tree, default is /' + type: string + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: boolean + secretFile: + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + secretRef: + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + required: + - monitors + type: object + cinder: + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: boolean + secretRef: + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + description: csi (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers (Beta feature). + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward API about the + pod that should populate this volume + properties: + defaultMode: + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents information + to create the file containing the pod field + properties: + fieldRef: + description: 'Required: Selects a field of the + pod: only annotations, labels, name and namespace + are supported.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must not + be absolute or contain the ''..'' path. Must + be utf-8 encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + emptyDir: + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + properties: + medium: + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: 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]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. + properties: + volumeClaimTemplate: + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + + Required, must not be nil. + properties: + metadata: + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. + type: object + spec: + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim + are also valid here. + properties: + accessModes: + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + items: + type: string + type: array + dataSource: + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + 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. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any 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. + 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 + required: + - kind + - name + type: object + resources: + description: |- + resources represents the minimum resources the volume should have. + If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + selector: + description: selector is a label query over + volumes to consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + type: string + volumeAttributesClassName: + description: |- + volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. + If specified, the CSI driver will create or update the volume with the attributes defined + in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, + it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass + will be applied to the claim but it's not allowed to reset this field to empty string once it is set. + If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass + will be set by the persistentvolume controller if it exists. + If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be + set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource + exists. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#volumeattributesclass + (Alpha) Using this field requires the VolumeAttributesClass feature gate to be enabled. + type: string + volumeMode: + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding reference + to the PersistentVolume backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource that + is attached to a kubelet's host machine and then exposed + to the pod. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + lun: + description: 'lun is Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC target worldwide + names (WWNs)' + items: + type: string + type: array + wwids: + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. + items: + type: string + type: array + type: object + flexVolume: + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. + properties: + driver: + description: driver is the name of the driver to use + for this volume. + type: string + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this field holds + extra command options if any.' + type: object + readOnly: + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + description: flocker represents a Flocker volume attached + to a kubelet's host machine. This depends on the Flocker + control service being running + properties: + datasetName: + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of the dataset. + This is unique identifier of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + properties: + fsType: + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + format: int32 + type: integer + pdName: + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: boolean + required: + - pdName + type: object + gitRepo: + description: |- + gitRepo represents a git repository at a particular revision. + DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. + properties: + directory: + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash for the specified + revision. + type: string + required: + - repository + type: object + glusterfs: + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/glusterfs/README.md + properties: + endpoints: + description: |- + endpoints is the endpoint name that details Glusterfs topology. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + path: + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + readOnly: + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- + TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not + mount host directories as read/write. + properties: + path: + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + type: + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + required: + - path + type: object + iscsi: + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://examples.k8s.io/volumes/iscsi/README.md + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether support + iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether support + iSCSI Session CHAP authentication + type: boolean + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + initiatorName: + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified Name. + type: string + iscsiInterface: + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret for iSCSI + target and initiator authentication + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + nfs: + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + properties: + path: + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + readOnly: + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: boolean + server: + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + properties: + claimName: + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + type: string + readOnly: + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: photonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets host + machine + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies Photon Controller + persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: portworxVolume represents a portworx volume + attached and mounted on kubelets host machine + properties: + fsType: + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one resources secrets, + configmaps, and downward API + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + sources: + description: sources is the list of volume projections + items: + description: Projection that may be projected along + with other supported volume types + properties: + clusterTrustBundle: + description: |- + ClusterTrustBundle allows a pod to access the `.spec.trustBundle` field + of ClusterTrustBundle objects in an auto-updating file. + + + Alpha, gated by the ClusterTrustBundleProjection feature gate. + + + ClusterTrustBundle objects can either be selected by name, or by the + combination of signer name and a label selector. + + + Kubelet performs aggressive normalization of the PEM contents written + into the pod filesystem. Esoteric PEM features such as inter-block + comments and block headers are stripped. Certificates are deduplicated. + The ordering of certificates within the file is arbitrary, and Kubelet + may change the order over time. + properties: + labelSelector: + description: |- + Select all ClusterTrustBundles that match this label selector. Only has + effect if signerName is set. Mutually-exclusive with name. If unset, + interpreted as "match nothing". If set but empty, interpreted as "match + everything". + 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 + name: + description: |- + Select a single ClusterTrustBundle by object name. Mutually-exclusive + with signerName and labelSelector. + type: string + optional: + description: |- + If true, don't block pod startup if the referenced ClusterTrustBundle(s) + aren't available. If using name, then the named ClusterTrustBundle is + allowed not to exist. If using signerName, then the combination of + signerName and labelSelector is allowed to match zero + ClusterTrustBundles. + type: boolean + path: + description: Relative path from the volume + root to write the bundle. + type: string + signerName: + description: |- + Select all ClusterTrustBundles that match this signer name. + Mutually-exclusive with name. The contents of all selected + ClusterTrustBundles will be unified and deduplicated. + type: string + required: + - path + type: object + configMap: + description: configMap information about the configMap + data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional specify whether the + ConfigMap or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about the + downwardAPI data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a field + of the pod: only annotations, labels, + name and namespace are supported.' + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in terms + of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to + select in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file to + be created. Must not be absolute or + contain the ''..'' path. Must be utf-8 + encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env + vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + secret: + description: secret information about the secret + data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional field specify whether + the Secret or its key must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information + about the serviceAccountToken data to project + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + format: int64 + type: integer + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + description: quobyte represents a Quobyte mount on the host + that shares a pod's lifetime + properties: + group: + description: |- + group to map volume access to + Default is no group + type: string + readOnly: + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes + type: string + tenant: + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: |- + user to map volume access to + Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references an already + created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/rbd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + image: + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + keyring: + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + monitors: + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + items: + type: string + type: array + pool: + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: boolean + secretRef: + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + required: + - image + - monitors + type: object + scaleIO: + description: scaleIO represents a ScaleIO persistent volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". + type: string + gateway: + description: gateway is the host address of the ScaleIO + API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name of the ScaleIO + Protection Domain for the configured storage. + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + description: sslEnabled Flag enable/disable SSL communication + with Gateway, default false + type: boolean + storageMode: + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO Storage Pool + associated with the protection domain. + type: string + system: + description: system is the name of the storage system + as configured in ScaleIO. + type: string + volumeName: + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: optional field specify whether the Secret + or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + type: object + storageos: + description: storageOS represents a StorageOS volume attached + and mounted on Kubernetes nodes. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. + type: string + volumeNamespace: + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: vsphereVolume represents a vSphere volume attached + and mounted on kubelets host machine + properties: + fsType: + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage Policy Based + Management (SPBM) profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage Policy + Based Management (SPBM) profile name. + type: string + volumePath: + description: volumePath is the path that identifies + vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + required: + - volumeMounts + - volumes + type: object + oneReplicaPerNode: + description: |- + oneReplicaPerNode controls whether to run 1 pod per node using the pod anti-affinity capability. + Enabling this configuration in an existing cluster will roll the cluster. + type: boolean + passwordEncoder: + description: passwordEncoder specifies password encoder secret for + Schema Registry. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer contains the directory path in the container where + the required secret is mounted. + Directory should have the file `password-encoder.txt`. The contents should include a new password. + Old password is optional and required only for rotation. + More info: https://docs.confluent.io/operator/current/co-password-encoder-secret. + type: string + secretRef: + description: |- + secretRef specifies the secret name. The secret should have the key + `password-encoder.txt`. The contents should include a new password. + Old password is optional and required only for rotation. + More info: https://docs.confluent.io/operator/current/co-password-encoder-secret. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + pdb: + description: |- + configures PodDisruptionBudget for the Confluent Platform component. + by default PDB is configured based on pre-detemined formula. + properties: + enabled: + description: enabled specifies whether the PodDisruptionBudget + is enabled + type: boolean + maxUnavailable: + description: maxUnavailable is the maximum number of pods that + can be unavailable during the disruption. + format: int32 + type: integer + required: + - enabled + type: object + podTemplate: + description: podTemplate specifies the statefulset pod template configuration. + properties: + affinity: + description: |- + affinity specifies a group of affinity scheduling rules. + More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + type: array + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + annotations: + additionalProperties: + type: string + description: |- + annotations is a map of string key and value pairs stored with the resource and + may be set by external tools to store and retrieve arbitrary metadata. They + are not queryable and should be preserved when modifying objects. More + info: http://kubernetes.io/docs/user-guide/annotations. + type: object + x-kubernetes-map-type: granular + envVars: + description: |- + envVars contain environment variables to be injected into containers. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container. + 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 + labels: + additionalProperties: + type: string + description: |- + labels is a map of string key and value pairs that can be used to organize and categorize + (scope and select) objects. + More info: http://kubernetes.io/docs/user-guide/labels. + type: object + x-kubernetes-map-type: granular + podSecurityContext: + description: |- + PodSecurityContext holds pod-level security attributes and common container settings. + Some fields are also present in container.securityContext. Field values of + container.securityContext take precedence over field values of PodSecurityContext. + 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: + + + 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---- + + + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + 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 + type: object + 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. + 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: + + + 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 + required: + - type + type: object + 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. + items: + format: int64 + type: integer + type: array + 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. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + 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. + 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 + type: object + type: object + priorityClassName: + description: priorityClassName specifies the priority class for + the pod (if any). + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + probe: + description: probe contains the fields for standard Kubernetes + readiness/liveness probe configuration. + properties: + liveness: + description: |- + liveness configures the Kubernetes probe settings. The changes + will override the existing default configuration. + properties: + failureThreshold: + description: |- + failureThreshold is the minimum consecutive failures for the probe to be considered failed. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + initialDelaySeconds: + description: |- + initialDelaySeconds is the number of seconds after the container has started and before probes are initiated. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + path: + description: Path for the HTTP probe + type: string + periodSeconds: + description: |- + periodSeconds specifies how often to perform the probe. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + port: + description: Number of the port to access on the container + type: integer + successThreshold: + description: |- + successThreshold is the minimum consecutive successes for the probe to be considered successful after having failed. + The default values is `1`. Must be `1` for liveness and startup. The minimum value is `1`. + format: int32 + type: integer + timeoutSeconds: + description: |- + timeoutSeconds is the number of seconds after which the probe times out. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + type: object + readiness: + description: |- + readiness configures the Kubernetes probe setting. The changes + will override the existing default configuration. + properties: + failureThreshold: + description: |- + failureThreshold is the minimum consecutive failures for the probe to be considered failed. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + initialDelaySeconds: + description: |- + initialDelaySeconds is the number of seconds after the container has started and before probes are initiated. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + path: + description: Path for the HTTP probe + type: string + periodSeconds: + description: |- + periodSeconds specifies how often to perform the probe. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + port: + description: Number of the port to access on the container + type: integer + successThreshold: + description: |- + successThreshold is the minimum consecutive successes for the probe to be considered successful after having failed. + The default values is `1`. Must be `1` for liveness and startup. The minimum value is `1`. + format: int32 + type: integer + timeoutSeconds: + description: |- + timeoutSeconds is the number of seconds after which the probe times out. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + type: object + type: object + resources: + description: resources describe 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. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: |- + SecurityContext holds security configuration that will be applied to a container. + Some fields are present in both SecurityContext and PodSecurityContext. When both + are set, the values in SecurityContext take precedence. + 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. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + 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 + type: object + 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. + 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: + + + 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 + required: + - type + type: object + 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. + 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 + type: object + type: object + serviceAccountName: + description: |- + ServiceAccountName is the name of the service account used to run this pod. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account. + type: string + terminationGracePeriodSeconds: + description: terminationGracePeriodSeconds is the grace period + before the pod is deleted. + format: int64 + type: integer + tolerations: + description: |- + tolerations specify the pods to schedule onto the nodes with matching taints, using + the triple `` and the matching operator ``. + 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 + topologySpreadConstraints: + description: |- + topologySpreadConstraints describe how a group of pods ought to spread across topology domains. Scheduler will + schedule pods based on the constraints. All topologySpreadConstraints are ANDed. + items: + description: TopologySpreadConstraint specifies how to spread + matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + 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. + + + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + 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. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + + + This is a beta field and requires the MinDomainsInPodTopologySpread feature gate to be enabled (enabled by default). + format: int32 + type: integer + 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. + + + 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. + + + 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 + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + replicas: + description: |- + replicas is the desired number of replicas. + A change to this setting will roll the cluster. + format: int32 + type: integer + telemetry: + description: telemetry specifies the Confluent telemetry reporter + configuration. + properties: + global: + description: |- + global allows disabling telemetry configuration. + If CFK is deployed with telemetry, this field is only + used to disable telemetry. The default value is `true` if + telemetry is enabled at the global level. + type: boolean + type: object + tls: + description: tls specifies the global TLS configurations for the REST + API endpoint. + properties: + autoGeneratedCerts: + description: |- + autoGeneratedCerts specifies that the certificates are auto-generated based on + the CA key pair provided. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + fips: + description: |- + fips specifies the configuration of FIPS compliant Bouncy Castle type Java Keystores for the cp component's + TLS settings. TLS Secrets must have the keys keystore.bcfks, truststore.bcfks, and jksPassword.txt + properties: + enabled: + description: enabled specifies whether to enable the FIPS + configuration for cp components. + type: boolean + required: + - enabled + type: object + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing the + JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + required: + - image + type: object + status: + description: status defines the observed state of the Schema Registry + cluster. + properties: + arbitraryData: + description: arbitraryData is the map for any arbitrary data associated + with this Confluent component. + x-kubernetes-preserve-unknown-fields: true + authorizationType: + description: authorizationType is the authorization type for this + Confluent component. + type: string + clusterName: + description: clusterName is the name of the Confluent Platform component + cluster. + type: string + clusterNamespace: + description: clusterNamespace is the namespace where the Confluent + Platform component cluster is running. + type: string + conditions: + description: conditions specify the latest available observations + of the current state. + items: + description: Condition represent the latest available observations + of the current state. + properties: + lastProbeTime: + description: lastProbeTime shows the last time the condition + was evaluated. + format: date-time + type: string + lastTransitionTime: + description: lastTransitionTime shows the last time the condition + was transitioned from one status to another. + format: date-time + type: string + message: + description: message shows a human-readable message with details + about the transition. + type: string + reason: + description: reason shows the reason for the last transition + of the condition. + type: string + status: + description: status shows the status of the condition, one of + `True`, `False`, or `Unknown`. + type: string + type: + description: type shows the condition type. + type: string + type: object + type: array + currentReplicas: + description: currentReplicas is the number of currently running replicas. + format: int32 + type: integer + groupId: + description: groupId is the group id of the Schema Registry cluster. + type: string + internalSecrets: + description: |- + internalSecrets are internal secrets created + by CFK for this Confluent component. + items: + type: string + type: array + internalTopicNames: + description: internalTopicNames are the topics used by the component + for internal use. + items: + type: string + type: array + kafka: + description: kafka is the Kafka client side status for the Schema + Registry cluster. + properties: + authenticationType: + description: authenticationType describes the authentication method + for the Kafka cluster. + type: string + bootstrapEndpoint: + description: bootstrapEndpoint specifies the Kafka bootstrap endpoint. + type: string + tls: + description: tls indicates whether TLS is enabled for the Kafka + dependency. + type: boolean + type: object + listeners: + additionalProperties: + description: ListenerStatus describes general information about + the listeners. + properties: + advertisedExternalEndpoints: + description: advertisedExternalEndpoints specifies other advertised + endpoints used, especially for Kafka. + items: + type: string + type: array + authenticationType: + description: authenticationType shows the authentication type + configured by the listener. + type: string + externalAccessType: + description: externalAccessType shows the external access type + used for the listener. + type: string + externalEndpoint: + description: externalEndpoint specifies the external endpoint + to connect to the Confluent component cluster. + type: string + internalEndpoint: + description: internalEndpoint specifies the internal endpoint + to connect to the Confluent component cluster. + type: string + tls: + description: tls shows whether TLS is configured for the listener. + type: boolean + type: object + description: listeners is a map of listener type and the status of + Schema Registry Listeners. + type: object + x-kubernetes-map-type: granular + metricPrefix: + description: metricPrefix is the prefix for the JMX metric of the + Schema Registry cluster. + type: string + observedGeneration: + description: observedGeneration is the most recent generation observed + for this Confluent component. + format: int64 + type: integer + operatorVersion: + description: operatorVersion is the internal version of CFK. + type: string + phase: + description: |- + phase describes the state of the Confluent Platform component. This can either be 'PROVISIONING' + or 'RUNNING' + 'PROVISIONING' means the Confluent Platform component is currently getting deployed and not ready yet. + 'RUNNING' means the Confluent Platform component has been successfully deployed. + type: string + rbac: + description: rbac contains the RBAC-related status when RBAC is enabled. + properties: + clusterID: + description: clusterID specifies the id of the cluster. + type: string + internalRolebindings: + description: internalRolebindings specifies the internal rolebindings. + items: + type: string + type: array + type: object + readyReplicas: + description: readyReplicas is the number of currently ready replicas. + format: int32 + type: integer + replicas: + description: replicas is the number of replicas. + format: int32 + type: integer + restConfig: + description: restConfig is the REST API configuration of the Schema + Registry cluster. + properties: + advertisedExternalEndpoints: + description: advertisedExternalEndpoints specifies other advertised + endpoints used, especially for Kafka. + items: + type: string + type: array + authenticationType: + description: authenticationType shows the authentication type + configured by the listener. + type: string + externalAccessType: + description: externalAccessType shows the external access type + used for the listener. + type: string + externalEndpoint: + description: externalEndpoint specifies the external endpoint + to connect to the Confluent component cluster. + type: string + internalEndpoint: + description: internalEndpoint specifies the internal endpoint + to connect to the Confluent component cluster. + type: string + tls: + description: tls shows whether TLS is configured for the listener. + type: boolean + type: object + selector: + description: |- + selector gets the label selector of the child pod. + The Horizontal Pod Autoscaler(HPA) will scale using the label selector of the child pod. + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + scale: + labelSelectorPath: .status.selector + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + status: {} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_schemas.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_schemas.yaml new file mode 100644 index 000000000..b50396d44 --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_schemas.yaml @@ -0,0 +1,590 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: schemas.platform.confluent.io +spec: + group: platform.confluent.io + names: + categories: + - all + - confluent-platform + - confluent + kind: Schema + listKind: SchemaList + plural: schemas + shortNames: + - schema + singular: schema + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.format + name: Format + type: string + - jsonPath: .status.id + name: ID + type: string + - jsonPath: .status.version + name: Version + type: string + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.schemaRegistryEndpoint + name: SchemaRegistryEndpoint + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired state of the Schema. + properties: + compatibilityLevel: + description: |- + compatibilityLevel specifies the compatibility level requirement for the schema under the specified subject. + Valid options are `BACKWARD`, `BACKWARD_TRANSITIVE`, `FORWARD`, `FORWARD_TRANSITIVE`, `FULL`, `FULL_TRANSITIVE` and `NONE`. + more info: https://docs.confluent.io/platform/current/schema-registry/avro.html#schema-evolution-and-compatibility + enum: + - BACKWARD + - BACKWARD_TRANSITIVE + - FORWARD + - FORWARD_TRANSITIVE + - FULL + - FULL_TRANSITIVE + - NONE + type: string + data: + description: data defines the data required to create the schema. + properties: + configRef: + description: configRef is the name of the Kubernetes ConfigMap + resource containing the schema. + minLength: 1 + type: string + format: + description: format is the format type of the encoded schema. + Valid options are `avro`, `json`, and `protobuf`. + enum: + - avro + - json + - protobuf + minLength: 1 + type: string + required: + - configRef + - format + type: object + mode: + description: |- + Mode specifies the schema registry mode for the schemas under the specified subject. + Valid options are `IMPORT`, `READONLY`, `READWRITE`. + enum: + - IMPORT + - READONLY + - READWRITE + type: string + name: + description: |- + name specifies the subject name of schema. If not configured, the Schema CR name is used + as the subject name. + maxLength: 255 + minLength: 1 + pattern: ^[^\\]*$ + type: string + normalize: + description: |- + Normalize specifies whether to normalize the schema at the time of registering to schema registry. + more info: https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/index.html#schema-normalization + type: boolean + schemaReferences: + description: schemaReferences defines the schema references in the + schema data. + items: + description: SchemaReference is the schema to be used as a reference + for the new schema. + properties: + avro: + description: avro is the data for the referenced Avro schema. + properties: + avro: + description: name is the fully qualified name of the referenced + Avro schema. + minLength: 1 + type: string + required: + - avro + type: object + format: + description: format is the format type of the referenced schema. + Valid options are `avro`, `json`, and `protobuf`. + enum: + - avro + - json + - protobuf + minLength: 1 + type: string + json: + description: json is the data for the referenced JSON schema. + properties: + url: + description: url is the referenced JSON schema url. + minLength: 1 + type: string + required: + - url + type: object + protobuf: + description: protobuf is the data for the referenced Protobuf + schema. + properties: + file: + description: file is the file name of the referenced Protobuf + schema. + minLength: 1 + type: string + required: + - file + type: object + subject: + description: subject is the subject name for the referenced + schema through the configRef. + minLength: 1 + type: string + version: + description: version is the version type of the referenced schema. + format: int32 + type: integer + required: + - format + - subject + - version + type: object + type: array + schemaRegistryClusterRef: + description: schemaRegistryClusterRef references the CFK-managed Schema + Registry cluster. + properties: + name: + description: name specifies the name of the Confluent Platform + component cluster. + type: string + namespace: + description: namespace specifies the namespace where the Confluent + Platform component cluster is running. + type: string + required: + - name + type: object + schemaRegistryRest: + description: schemaRegistryRest specifies the Schema Registry REST + API configuration. + properties: + authentication: + description: authentication specifies the REST API authentication + mechanism. + properties: + basic: + description: basic specifies the basic authentication settings + for the REST API client. + properties: + debug: + description: debug enables the basic authentication debug + logs for JaaS configuration. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer allows to pass the basic credential through a directory path in the container. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + minLength: 1 + type: string + restrictedRoles: + description: |- + restrictedRoles specify the restricted roles on the server side only. + Changes will be only reflected in Control Center. + This configuration is ignored on the client side configuration. + items: + type: string + minItems: 1 + type: array + roles: + description: |- + roles specify the roles on the server side only. + This configuration is ignored on the client side configuration. + items: + type: string + type: array + secretRef: + description: |- + secretRef defines secret reference to pass the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#basic-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + bearer: + description: bearer specifies the bearer authentication settings + for the REST API client. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container + where the credential is mounted. + minLength: 1 + type: string + secretRef: + description: |- + secretRef specifies the name of the secret that contains the credential. + More info: https://docs.confluent.io/operator/current/co-authenticate.html#bearer-authentication + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauth: + description: oauth specifies the OAuth authentication settings + for the REST API client. + properties: + configuration: + description: configuration specifies the OAuth server + settings. + properties: + audience: + description: audience specifies the audience claim + in the JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected + issuer in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of + claim in token for identifying the groups of subject + in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout + with IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry + backoff with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff + with IDP in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim + in JWT to use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + directoryPathInContainer: + description: directoryPathInContainer allows to pass the + basic credential through a directory path in the container. + minLength: 1 + type: string + secretRef: + description: secretRef defines secret reference to pass + the required credentials. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - configuration + type: object + type: + description: type specifies the REST API authentication type. + Valid options are `basic`, `bearer`, `mtls` and `oauth`. + enum: + - basic + - bearer + - mtls + - oauth + type: string + required: + - type + type: object + endpoint: + description: endpoint specifies where Confluent REST API is running. + minLength: 1 + pattern: ^https?://.* + type: string + kafkaClusterID: + description: |- + kafkaClusterID specifies the id of Kafka cluster. + It takes precedence over using the Kafka REST API to get the cluster id. + minLength: 1 + type: string + tls: + description: "tls specifies the custom TLS structure for the application + resources,\n\t// e.g. connector, topic, schema, of the Confluent + Platform components.\n\t// +optional" + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer contains the directory path in the container where + `keystore.jks`, `truststore.jks`, `jksPassword.txt` keys are mounted. + minLength: 1 + type: string + jksPassword: + description: jksPassword specifies the secret name that contains + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef specifies the secret name that contains the certificates. + More info about certificates key/value format: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + type: object + required: + - data + type: object + status: + description: status defines the observed state of the Schema. + properties: + appState: + default: Unknown + description: appState is the current state of the Schema application. + enum: + - Unknown + - Created + - Failed + - Deleted + type: string + compatibilityLevel: + description: compatibilityLevel specifies the compatibility level + of the schema under the subject. + type: string + conditions: + description: conditions are the latest available observed state of + the schema. + items: + description: Condition represent the latest available observations + of the current state. + properties: + lastProbeTime: + description: lastProbeTime shows the last time the condition + was evaluated. + format: date-time + type: string + lastTransitionTime: + description: lastTransitionTime shows the last time the condition + was transitioned from one status to another. + format: date-time + type: string + message: + description: message shows a human-readable message with details + about the transition. + type: string + reason: + description: reason shows the reason for the last transition + of the condition. + type: string + status: + description: status shows the status of the condition, one of + `True`, `False`, or `Unknown`. + type: string + type: + description: type shows the condition type. + type: string + type: object + type: array + deletedVersions: + description: deletedVersions are the successfully hard deleted versions + for the subject. + items: + format: int32 + type: integer + type: array + format: + description: format is the format of the latest schema for the subject. + type: string + id: + description: id is the id of the latest schema for the subject. + format: int32 + type: integer + mode: + description: Mode specifies the operating mode of schema under the + subject. + type: string + normalize: + description: Normalize specifies whether schema has been normalized + at the time of registering. + type: boolean + observedGeneration: + description: observedGeneration is the most recent generation observed + for this Confluent component. + format: int64 + type: integer + schemaReferences: + description: schemaReferences are the schema references for the subject. + items: + description: SchemaReference is the schema to be used as a reference + for the new schema. + properties: + avro: + description: avro is the data for the referenced Avro schema. + properties: + avro: + description: name is the fully qualified name of the referenced + Avro schema. + minLength: 1 + type: string + required: + - avro + type: object + format: + description: format is the format type of the referenced schema. + Valid options are `avro`, `json`, and `protobuf`. + enum: + - avro + - json + - protobuf + minLength: 1 + type: string + json: + description: json is the data for the referenced JSON schema. + properties: + url: + description: url is the referenced JSON schema url. + minLength: 1 + type: string + required: + - url + type: object + protobuf: + description: protobuf is the data for the referenced Protobuf + schema. + properties: + file: + description: file is the file name of the referenced Protobuf + schema. + minLength: 1 + type: string + required: + - file + type: object + subject: + description: subject is the subject name for the referenced + schema through the configRef. + minLength: 1 + type: string + version: + description: version is the version type of the referenced schema. + format: int32 + type: integer + required: + - format + - subject + - version + type: object + type: array + schemaRegistryAuthenticationType: + description: schemaRegistryAuthenticationType is the authentication + method used. + type: string + schemaRegistryEndpoint: + description: schemaRegistryEndpoint is the Schema Registry REST endpoint. + type: string + schemaRegistryTLS: + description: schemaRegistryTLS shows whether the Schema Registry is + using TLS. + type: boolean + softDeletedVersions: + description: softDeletedVersions are the successfully soft deleted + versions for the subject. + items: + format: int32 + type: integer + type: array + state: + description: state is the state of the Schema CR. + type: string + subject: + description: subject is the subject of the schema. + type: string + version: + description: version is the version of the latest schema for the subject. + format: int32 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_zookeepers.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_zookeepers.yaml new file mode 100644 index 000000000..5a89c94c7 --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/crds/platform.confluent.io_zookeepers.yaml @@ -0,0 +1,4713 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: zookeepers.platform.confluent.io +spec: + group: platform.confluent.io + names: + categories: + - all + - confluent-platform + - confluent + kind: Zookeeper + listKind: ZookeeperList + plural: zookeepers + shortNames: + - zookeeper + - zk + singular: zookeeper + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.replicas + name: Replicas + type: string + - jsonPath: .status.readyReplicas + name: Ready + type: string + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.endpoint + name: Endpoint + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: Zookeeper is the schema for the Zookeeper 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: spec defines the desired state of the Zookeeper cluster. + properties: + authentication: + description: authentication specifies the authentication configuration. + properties: + jaasConfig: + description: |- + jaasConfig specifies the JaaS configuration. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + properties: + secretRef: + description: |- + secretRef references the secret containing the required credentials. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + jaasConfigPassThrough: + description: |- + jaasConfigPassThrough specifies another way to provide JaaS configuration. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where required credentials are mounted. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + minLength: 1 + type: string + secretRef: + description: |- + secretRef references the secret containing the required credentials for authentication. + More info: https://docs.confluent.io/operator/current/co-authenticate.html + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + oauthSettings: + description: |- + oauthSettings specifies the OAuth settings. + This needs to passed with the authentication type `oauth`. + properties: + audience: + description: audience specifies the audience claim in the + JWT payload. + minLength: 1 + type: string + expectedIssuer: + description: expectedIssuer specifies the expected issuer + in the JWT payload. + minLength: 1 + type: string + groupsClaimName: + description: groupsClaimName specifies the name of claim in + token for identifying the groups of subject in the JWT payload. + minLength: 1 + type: string + jwksEndpointUri: + description: |- + jwksEndpointUri specifies the uri for the JSON Web Key Set (JWKS). + It is used to get set of keys containing the public keys used to verify any JWT issued by the IdP's Authorization Server. + minLength: 1 + type: string + loginConnectTimeoutMs: + description: LoginConnectTimeoutMs sets connect timeout with + IDP in ms + format: int32 + type: integer + loginReadTimeoutMs: + description: LoginReadTimeoutMs sets read timeout with IDP + in ms + format: int32 + type: integer + loginRetryBackoffMaxMs: + description: LoginRetryBackoffMaxMs sets max retry backoff + with IDP in ms + format: int32 + type: integer + loginRetryBackoffMs: + description: LoginRetryBackoffMs sets retry backoff with IDP + in ms + format: int32 + type: integer + scope: + description: |- + scope is optional and required only when your identity provider doesn't have + a default scope or your groups claim is linked to a scope. + minLength: 1 + type: string + subClaimName: + description: subClaimName specifies name of claim in JWT to + use for the subject. + minLength: 1 + type: string + tokenEndpointUri: + description: |- + tokenBaseEndpointUri specifies the base uri for token endpoint. + This is required for OAuth for inter broker communication along with + clientId & clientSecret in JassConfig or JassConfigPassthrough + minLength: 1 + type: string + type: object + principalMappingRules: + items: + type: string + type: array + type: + description: |- + type specifies the Kafka or Zookeeper authentication type. + Valid options are `plain`, `digest`, `mtls`, `ldap` & `oauth`. + enum: + - plain + - digest + - mtls + - ldap + - oauth + type: string + required: + - type + type: object + configOverrides: + description: |- + configOverrides specifies configs to override the server/JVM/log4j/peer + properties for the Zookeeper cluster. + A change to this property will roll the cluster. + properties: + jvm: + description: |- + jvm is a list of JVM configuration supported by the Confluent Platform component. + This will either add or update the existing configuration. + items: + type: string + type: array + log4j: + description: |- + log4j is a list of Log4J configuration supported by the Confluent Platform component. + This will either add or update the existing configuration. + items: + type: string + type: array + peers: + description: |- + peers specify a list of dynamic peer configurations for the Zookeeper cluster. This is only required when deploying stretch + Zookeeper for MRC deployments and should include all the Zookeeper peers in other DCs that form the ensemble. + This will either add or update the existing configuration. + items: + type: string + type: array + server: + description: |- + server is a list of server configuration supported by the Confluent Platform component. + This will either add or update existing configuration. + items: + type: string + type: array + type: object + dataVolumeCapacity: + anyOf: + - type: integer + - type: string + description: dataVolumeCapacity specifies the data volume size. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + externalAccess: + description: |- + externalAccess specifies the external access configuration. + Should only be specified when Zookeeper peers are on another network. + properties: + loadBalancer: + description: loadBalancer specifies the configuration to create + a Kubernetes load balancer service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and value + pairs. It specifies Kubernetes annotations for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain is the domain name of the component cluster. + minLength: 1 + type: string + externalTrafficPolicy: + description: externalTrafficPolicy specifies the external + traffic policy for the service. Valid options are `Local` + and `Cluster`. + enum: + - Local + - Cluster + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value pairs. + It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + loadBalancerSourceRanges: + description: loadBalancerSourceRanges specify the source ranges. + items: + type: string + type: array + port: + description: |- + port specifies the external port for the client consumption. + If not configured, the same internal/external port is configured for the component. + Information about the port can be retrieved through the status API. + format: int32 + type: integer + prefix: + description: |- + prefix specify the prefix for the given domain. + The default value is the name of the cluster. + minLength: 1 + type: string + servicePorts: + description: servicePorts specify the user-provided service + port(s). + items: + description: ServicePort contains information on service's + port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations of Client + IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - domain + type: object + nodePort: + description: nodePort specifies the configuration to create a + Kubernetes node port service. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to `://:, where`podId` starts from `0` to `replicaCount - 1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and value + pairs. It specifies Kubernetes annotations for this service. + type: object + x-kubernetes-map-type: granular + externalTrafficPolicy: + description: |- + externalTrafficPolicy specifies the external traffic policy for the service. + Valid options are `Local` and `Cluster`. + enum: + - Local + - Cluster + type: string + host: + description: host defines the host name of the cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value pairs. + It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + nodePortOffset: + description: |- + nodePortOffset specifies the starting offset of the node ports. The port numbers go in ascending order with respect + to the replicas count. + NodePort service creation fails if the node port is not in the range supported by the Kubernetes API server. + The default Kubernetes Node Port range is `30000` - `32762`. + format: int32 + minimum: 0 + type: integer + servicePorts: + description: |- + servicePorts specify user-provided service port(s). + For Kafka with the nodePort type, this setting is only applied to Kafka bootstrap service. + items: + description: ServicePort contains information on service's + port. + properties: + appProtocol: + description: |- + The application protocol for this port. + This is used as a hint for implementations to offer richer behavior for protocols that they understand. + This field follows standard Kubernetes label syntax. + Valid values are either: + + + * Un-prefixed protocol names - reserved for IANA standard service names (as per + RFC-6335 and https://www.iana.org/assignments/service-names). + + + * Kubernetes-defined prefixed names: + * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- + * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 + * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 + + + * Other protocols should use implementation-defined prefixed names such as + mycompany.com/my-custom-protocol. + type: string + name: + description: |- + The name of this port within the service. This must be a DNS_LABEL. + All ports within a ServiceSpec must have unique names. When considering + the endpoints for a Service, this must match the 'name' field in the + EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: |- + The port on each node on which this service is exposed when type is + NodePort or LoadBalancer. Usually assigned by the system. If a value is + specified, in-range, and not in use it will be used, otherwise the + operation will fail. If not specified, a port will be allocated if this + Service requires one. If this field is specified when creating a + Service which does not need it, creation will fail. This field will be + wiped when updating a Service to no longer need it (e.g. changing type + from NodePort to ClusterIP). + More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + format: int32 + type: integer + port: + description: The port that will be exposed by this service. + format: int32 + type: integer + protocol: + default: TCP + description: |- + The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". + Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the pods targeted by the service. + Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + If this is a string, it will be looked up as a named port in the + target Pod's container ports. If this is not specified, the value + of the 'port' field is used (an identity map). + This field is ignored for services with clusterIP=None, and should be + omitted or set equal to the 'port' field. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + sessionAffinity: + description: |- + sessionAffinity defines the Kubernetes session affinity. The valid options are `ClientIP` and `None`. `ClientIP` enables the client IP-based session affinity. + The default value is `None`. + More info: https://kubernetes.io/docs/reference/networking/virtual-ips/#session-affinity. + enum: + - ClientIP + - None + type: string + sessionAffinityConfig: + description: SessionAffinityConfig contains the configurations + of the session affinity. + properties: + clientIP: + description: clientIP contains the configurations of Client + IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + required: + - host + - nodePortOffset + type: object + route: + description: route specifies the configuration to create a route + service in OpenShift. + properties: + advertisedURL: + description: |- + advertisedURL specifies the configuration for advertised listener per pod. It is only supported for MDS currently. + If it is enabled, instead of using internal endpoint, the MDS advertised listener for each broker will be + set to: `://-http-external.` where podId starts from `0` to `replicaCount -1`. + This is only recommended if you cannot add internal SANs to the TLS certificates for MDS and + the external DNS must be resolved inside the Kubernetes cluster. + This configuration will not take effect if MDS enabled dual listener setup. + properties: + enabled: + description: |- + enabled indicates whether to set the MDS advertised listener url with external endpoint for each broker. + Has no effect with Zookeeper, which will always create a listener per pod. + type: boolean + prefix: + description: |- + prefix specifies the broker prefix for MDS/Zookeeper advertised endpoint. + If not configured, it uses `b` as default prefix for MDS, such as `b#.domain` where `#` will start from `0` to `replicaCount -1`. + It uses 'zookeeper' as default prefix for Zookeeper in the same way. + minLength: 1 + type: string + required: + - enabled + type: object + annotations: + additionalProperties: + type: string + description: annotations is a map of string key and value + pairs. It specifies Kubernetes annotations for this service. + type: object + x-kubernetes-map-type: granular + domain: + description: domain specifies the domain name of the Confluent + component cluster. + minLength: 1 + type: string + labels: + additionalProperties: + type: string + description: labels is a map of string key and value pairs. + It specifies Kubernetes labels for this service. + type: object + x-kubernetes-map-type: granular + prefix: + description: |- + prefix specifies the component prefix when configured for the domain. + The default value is the name of the cluster. + minLength: 1 + type: string + wildcardPolicy: + description: |- + wildcardPolicy allows you to define a route that covers all hosts within a domain. Valid options are `Subdomain` and `None`. + The default value is `None`. + enum: + - Subdomain + - None + type: string + required: + - domain + type: object + type: + description: |- + type specifies the Kubernetes external service for the component. + Valid options are `loadBalancer`, `nodePort`, and `route`. + enum: + - loadBalancer + - nodePort + - route + minLength: 1 + type: string + required: + - type + type: object + headlessService: + description: headlessService specifies the configuration of the Kubernetes + headless service. + properties: + annotations: + additionalProperties: + type: string + description: |- + annotations is a map of string key and value pairs. + It specifies the annotations to be added to the CFK-created headless service. + These annotations are merged with the injectAnnotations and take precedence. + type: object + x-kubernetes-map-type: granular + labels: + additionalProperties: + type: string + description: |- + labels is a map of string key and value pairs. + It specifies the labels to be added to the CFK-created headless service. + These labels are merged with the injectLabels and take precedence. + type: object + x-kubernetes-map-type: granular + publishNotReadyAddresses: + description: |- + publishNotReadyAddresses specifies the publishNotReadyAddresses field. + For Kafka, this value must be true. The default value is true. + type: boolean + type: object + image: + description: |- + image specifies the application and the init docker image configurations. + A change to this setting will roll the cluster. + properties: + application: + description: |- + application is the Docker image name of the application. Specify + `//:`. + pattern: .+:.+ + type: string + init: + description: |- + init is the init-container name. Specify + `//:`. + pattern: .+:.+ + type: string + pullPolicy: + description: |- + pullPolicy is the policy for pulling images. Valid options are `Always`, `Never`, and `IfNotPresent`. + The default value is `IfNotPresent`. + enum: + - Always + - Never + - IfNotPresent + type: string + pullSecretRef: + description: |- + pullSecretRef references the secrets in the same namespace to be used for pulling images. + Image pull secrets are distinct from secrets because secrets + can be mounted in the pod, but image pull secrets are only accessed by `kubelet`. + More info: https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod + items: + type: string + type: array + required: + - application + - init + type: object + injectAnnotations: + additionalProperties: + type: string + description: |- + injectAnnotations are the annotations injected to the internal resources that CFK created. + The internal annotations are preserved and cannot be overridden. + For pod annotations, use `podTemplate.annotations`. + type: object + x-kubernetes-map-type: granular + injectLabels: + additionalProperties: + type: string + description: |- + injectLabels are the labels injected to the internal resources that CFK created. + The internal labels are preserved and cannot be overridden. + For pod labels, use `podTemplate.labels`. + type: object + x-kubernetes-map-type: granular + k8sClusterDomain: + description: |- + k8sClusterDomain specifies the configuration of the Kubernetes cluster domain. + The default is the `cluster.local` domain. + type: string + license: + description: license specifies the license configuration for the Confluent + Platform component. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + the license key is mounted. More info: + https://docs.confluent.io/operator/current/co-license.html#update-component-level-licenses + minLength: 1 + type: string + globalLicense: + description: globalLicense specifies whether the Confluent Platform + component shares the common global license. + type: boolean + secretRef: + description: |- + secretRef references the secret that provides the license for the Confluent Platform component. + More info: https://docs.confluent.io/operator/current/co-license.html#update-component-level-licenses + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + logVolumeCapacity: + anyOf: + - type: integer + - type: string + description: logVolumeCapacity specifies the log volume size. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + metrics: + description: metrics specify the security settings for the metric + services. + properties: + authentication: + description: authentication specifies the authentication configuration + for the metrics. + properties: + type: + description: type specifies the metrics authentication method. + The valid option is `mtls`. + enum: + - mtls + type: string + required: + - type + type: object + prometheus: + description: prometheus specifies the configuration overrides + for the JMX-Prometheus exporter. + properties: + blacklist: + items: + type: string + type: array + rules: + items: + description: Rule defines the Prometheus Exporter rule override. + properties: + attrNameSnakeCase: + type: boolean + cache: + type: boolean + help: + minLength: 1 + type: string + labels: + additionalProperties: + type: string + type: object + x-kubernetes-map-type: granular + name: + minLength: 1 + type: string + pattern: + minLength: 1 + type: string + type: + minLength: 1 + type: string + value: + minLength: 1 + type: string + valueFactor: + anyOf: + - type: integer + - type: string + default: 1 + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: array + whitelist: + items: + type: string + type: array + type: object + tls: + description: tls specifies the TLS configuration for the metrics. + properties: + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + enabled: + description: enabled specifies to enable the TLS configuration + for the Confluent component. + type: boolean + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing + the JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - enabled + type: object + type: object + mountedSecrets: + description: |- + mountedSecrets list the secrets injected to + the underlying statefulset configuration. The secret reference is mounted + in the default path `/mnt/secrets/`. The underlying resources + will follow the secret as a file configuration. + More info: https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets-as-files-from-a-pod. + A change to this setting will roll the cluster. + items: + description: |- + MountedSecrets provides a way to inject a custom secret to the underlying + statefulset. + properties: + keyItems: + description: keyItems are key and path names. + items: + description: Maps a string key to a path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + secretRef: + description: secretRef references the name of the secret. + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + type: array + mountedVolumes: + description: |- + mountedVolumes list the custom volumes that need to be mounted into the + underlying statefulset. + A change to this setting will roll the cluster. + properties: + volumeMounts: + description: |- + volumeMounts specify the list of volume mounts for the pods in the + statefulset. + items: + description: VolumeMount describes a mounting of a Volume within + a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + description: |- + volumes specify the list of volumes that can be mounted into the pods + of statefulset. + items: + description: Volume represents a named volume in a pod that + may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + format: int32 + type: integer + readOnly: + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: boolean + volumeID: + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + required: + - volumeID + type: object + azureDisk: + description: azureDisk represents an Azure Data Disk mount + on the host and bind mount to the pod. + properties: + cachingMode: + description: 'cachingMode is the Host Caching mode: + None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the data disk in + the blob storage + type: string + diskURI: + description: diskURI is the URI of data disk in the + blob storage + type: string + fsType: + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: multiple + blob disks per storage account Dedicated: single + blob disk per storage account Managed: azure managed + data disk (only in managed availability set). defaults + to shared' + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: azureFile represents an Azure File Service + mount on the host and bind mount to the pod. + properties: + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of secret that + contains Azure Storage Account Name and Key + type: string + shareName: + description: shareName is the azure share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: cephFS represents a Ceph FS mount on the host + that shares a pod's lifetime + properties: + monitors: + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + items: + type: string + type: array + path: + description: 'path is Optional: Used as the mounted + root, rather than the full Ceph tree, default is /' + type: string + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: boolean + secretFile: + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + secretRef: + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + required: + - monitors + type: object + cinder: + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: boolean + secretRef: + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + description: csi (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers (Beta feature). + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward API about the + pod that should populate this volume + properties: + defaultMode: + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents information + to create the file containing the pod field + properties: + fieldRef: + description: 'Required: Selects a field of the + pod: only annotations, labels, name and namespace + are supported.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must not + be absolute or contain the ''..'' path. Must + be utf-8 encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + emptyDir: + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + properties: + medium: + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: 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]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. + properties: + volumeClaimTemplate: + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + + Required, must not be nil. + properties: + metadata: + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. + type: object + spec: + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim + are also valid here. + properties: + accessModes: + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + items: + type: string + type: array + dataSource: + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + 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. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any 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. + 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 + required: + - kind + - name + type: object + resources: + description: |- + resources represents the minimum resources the volume should have. + If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + selector: + description: selector is a label query over + volumes to consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + type: string + volumeAttributesClassName: + description: |- + volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. + If specified, the CSI driver will create or update the volume with the attributes defined + in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, + it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass + will be applied to the claim but it's not allowed to reset this field to empty string once it is set. + If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass + will be set by the persistentvolume controller if it exists. + If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be + set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource + exists. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#volumeattributesclass + (Alpha) Using this field requires the VolumeAttributesClass feature gate to be enabled. + type: string + volumeMode: + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding reference + to the PersistentVolume backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource that + is attached to a kubelet's host machine and then exposed + to the pod. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + lun: + description: 'lun is Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC target worldwide + names (WWNs)' + items: + type: string + type: array + wwids: + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. + items: + type: string + type: array + type: object + flexVolume: + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. + properties: + driver: + description: driver is the name of the driver to use + for this volume. + type: string + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this field holds + extra command options if any.' + type: object + readOnly: + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + description: flocker represents a Flocker volume attached + to a kubelet's host machine. This depends on the Flocker + control service being running + properties: + datasetName: + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of the dataset. + This is unique identifier of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + properties: + fsType: + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + format: int32 + type: integer + pdName: + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: boolean + required: + - pdName + type: object + gitRepo: + description: |- + gitRepo represents a git repository at a particular revision. + DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. + properties: + directory: + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash for the specified + revision. + type: string + required: + - repository + type: object + glusterfs: + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/glusterfs/README.md + properties: + endpoints: + description: |- + endpoints is the endpoint name that details Glusterfs topology. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + path: + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + readOnly: + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- + TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not + mount host directories as read/write. + properties: + path: + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + type: + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + required: + - path + type: object + iscsi: + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://examples.k8s.io/volumes/iscsi/README.md + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether support + iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether support + iSCSI Session CHAP authentication + type: boolean + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + initiatorName: + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified Name. + type: string + iscsiInterface: + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret for iSCSI + target and initiator authentication + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + nfs: + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + properties: + path: + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + readOnly: + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: boolean + server: + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + properties: + claimName: + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + type: string + readOnly: + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: photonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets host + machine + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies Photon Controller + persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: portworxVolume represents a portworx volume + attached and mounted on kubelets host machine + properties: + fsType: + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one resources secrets, + configmaps, and downward API + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + sources: + description: sources is the list of volume projections + items: + description: Projection that may be projected along + with other supported volume types + properties: + clusterTrustBundle: + description: |- + ClusterTrustBundle allows a pod to access the `.spec.trustBundle` field + of ClusterTrustBundle objects in an auto-updating file. + + + Alpha, gated by the ClusterTrustBundleProjection feature gate. + + + ClusterTrustBundle objects can either be selected by name, or by the + combination of signer name and a label selector. + + + Kubelet performs aggressive normalization of the PEM contents written + into the pod filesystem. Esoteric PEM features such as inter-block + comments and block headers are stripped. Certificates are deduplicated. + The ordering of certificates within the file is arbitrary, and Kubelet + may change the order over time. + properties: + labelSelector: + description: |- + Select all ClusterTrustBundles that match this label selector. Only has + effect if signerName is set. Mutually-exclusive with name. If unset, + interpreted as "match nothing". If set but empty, interpreted as "match + everything". + 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 + name: + description: |- + Select a single ClusterTrustBundle by object name. Mutually-exclusive + with signerName and labelSelector. + type: string + optional: + description: |- + If true, don't block pod startup if the referenced ClusterTrustBundle(s) + aren't available. If using name, then the named ClusterTrustBundle is + allowed not to exist. If using signerName, then the combination of + signerName and labelSelector is allowed to match zero + ClusterTrustBundles. + type: boolean + path: + description: Relative path from the volume + root to write the bundle. + type: string + signerName: + description: |- + Select all ClusterTrustBundles that match this signer name. + Mutually-exclusive with name. The contents of all selected + ClusterTrustBundles will be unified and deduplicated. + type: string + required: + - path + type: object + configMap: + description: configMap information about the configMap + data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional specify whether the + ConfigMap or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about the + downwardAPI data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a field + of the pod: only annotations, labels, + name and namespace are supported.' + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in terms + of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to + select in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file to + be created. Must not be absolute or + contain the ''..'' path. Must be utf-8 + encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env + vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + type: object + secret: + description: secret information about the secret + data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: optional field specify whether + the Secret or its key must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information + about the serviceAccountToken data to project + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + format: int64 + type: integer + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + description: quobyte represents a Quobyte mount on the host + that shares a pod's lifetime + properties: + group: + description: |- + group to map volume access to + Default is no group + type: string + readOnly: + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes + type: string + tenant: + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: |- + user to map volume access to + Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references an already + created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/rbd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem from compromising the machine + type: string + image: + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + keyring: + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + monitors: + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + items: + type: string + type: array + pool: + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: boolean + secretRef: + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + required: + - image + - monitors + type: object + scaleIO: + description: scaleIO represents a ScaleIO persistent volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". + type: string + gateway: + description: gateway is the host address of the ScaleIO + API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name of the ScaleIO + Protection Domain for the configured storage. + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + description: sslEnabled Flag enable/disable SSL communication + with Gateway, default false + type: boolean + storageMode: + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO Storage Pool + associated with the protection domain. + type: string + system: + description: system is the name of the storage system + as configured in ScaleIO. + type: string + volumeName: + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: optional field specify whether the Secret + or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + type: object + storageos: + description: storageOS represents a StorageOS volume attached + and mounted on Kubernetes nodes. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. + type: string + volumeNamespace: + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: vsphereVolume represents a vSphere volume attached + and mounted on kubelets host machine + properties: + fsType: + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage Policy Based + Management (SPBM) profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage Policy + Based Management (SPBM) profile name. + type: string + volumePath: + description: volumePath is the path that identifies + vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + required: + - volumeMounts + - volumes + type: object + oneReplicaPerNode: + description: |- + oneReplicaPerNode controls whether to run 1 pod per node using the pod anti-affinity capability. + Enabling this configuration in an existing cluster will roll the cluster. + type: boolean + pdb: + description: |- + configures PodDisruptionBudget for the Confluent Platform component. + by default PDB is configured based on pre-detemined formula. + properties: + enabled: + description: enabled specifies whether the PodDisruptionBudget + is enabled + type: boolean + maxUnavailable: + description: maxUnavailable is the maximum number of pods that + can be unavailable during the disruption. + format: int32 + type: integer + required: + - enabled + type: object + peers: + description: |- + peers specify a list of dynamic peer configurations for the Zookeeper cluster. This is only required when deploying stretch + Zookeeper for MRC deployments and should include all the Zookeeper peers in other DCs that form the ensemble. + This will either add or update the existing configuration. + items: + type: string + type: array + podTemplate: + description: podTemplate specifies the statefulset pod template configuration. + properties: + affinity: + description: |- + affinity specifies a group of affinity scheduling rules. + More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + x-kubernetes-map-type: atomic + type: array + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + Also, MatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `LabelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both MismatchLabelKeys and LabelSelector. + Also, MismatchLabelKeys cannot be set when LabelSelector isn't set. + This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + annotations: + additionalProperties: + type: string + description: |- + annotations is a map of string key and value pairs stored with the resource and + may be set by external tools to store and retrieve arbitrary metadata. They + are not queryable and should be preserved when modifying objects. More + info: http://kubernetes.io/docs/user-guide/annotations. + type: object + x-kubernetes-map-type: granular + envVars: + description: |- + envVars contain environment variables to be injected into containers. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container. + 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 + labels: + additionalProperties: + type: string + description: |- + labels is a map of string key and value pairs that can be used to organize and categorize + (scope and select) objects. + More info: http://kubernetes.io/docs/user-guide/labels. + type: object + x-kubernetes-map-type: granular + podSecurityContext: + description: |- + PodSecurityContext holds pod-level security attributes and common container settings. + Some fields are also present in container.securityContext. Field values of + container.securityContext take precedence over field values of PodSecurityContext. + 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: + + + 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---- + + + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + 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 + type: object + 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. + 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: + + + 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 + required: + - type + type: object + 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. + items: + format: int64 + type: integer + type: array + 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. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + 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. + 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 + type: object + type: object + priorityClassName: + description: priorityClassName specifies the priority class for + the pod (if any). + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + probe: + description: probe contains the fields for standard Kubernetes + readiness/liveness probe configuration. + properties: + liveness: + description: |- + liveness configures the Kubernetes probe settings. The changes + will override the existing default configuration. + properties: + failureThreshold: + description: |- + failureThreshold is the minimum consecutive failures for the probe to be considered failed. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + initialDelaySeconds: + description: |- + initialDelaySeconds is the number of seconds after the container has started and before probes are initiated. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + path: + description: Path for the HTTP probe + type: string + periodSeconds: + description: |- + periodSeconds specifies how often to perform the probe. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + port: + description: Number of the port to access on the container + type: integer + successThreshold: + description: |- + successThreshold is the minimum consecutive successes for the probe to be considered successful after having failed. + The default values is `1`. Must be `1` for liveness and startup. The minimum value is `1`. + format: int32 + type: integer + timeoutSeconds: + description: |- + timeoutSeconds is the number of seconds after which the probe times out. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + type: object + readiness: + description: |- + readiness configures the Kubernetes probe setting. The changes + will override the existing default configuration. + properties: + failureThreshold: + description: |- + failureThreshold is the minimum consecutive failures for the probe to be considered failed. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + initialDelaySeconds: + description: |- + initialDelaySeconds is the number of seconds after the container has started and before probes are initiated. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + path: + description: Path for the HTTP probe + type: string + periodSeconds: + description: |- + periodSeconds specifies how often to perform the probe. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + port: + description: Number of the port to access on the container + type: integer + successThreshold: + description: |- + successThreshold is the minimum consecutive successes for the probe to be considered successful after having failed. + The default values is `1`. Must be `1` for liveness and startup. The minimum value is `1`. + format: int32 + type: integer + timeoutSeconds: + description: |- + timeoutSeconds is the number of seconds after which the probe times out. + Confluent Platform components come with the right configuration, and this setting is not required to change most of the time. + format: int32 + type: integer + type: object + type: object + resources: + description: resources describe 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. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: |- + SecurityContext holds security configuration that will be applied to a container. + Some fields are present in both SecurityContext and PodSecurityContext. When both + are set, the values in SecurityContext take precedence. + 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. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + 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. + format: int64 + type: integer + 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. + format: int64 + type: integer + 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. + 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 + type: object + 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. + 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: + + + 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 + required: + - type + type: object + 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. + 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 + type: object + type: object + serviceAccountName: + description: |- + ServiceAccountName is the name of the service account used to run this pod. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account. + type: string + terminationGracePeriodSeconds: + description: terminationGracePeriodSeconds is the grace period + before the pod is deleted. + format: int64 + type: integer + tolerations: + description: |- + tolerations specify the pods to schedule onto the nodes with matching taints, using + the triple `` and the matching operator ``. + 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 + topologySpreadConstraints: + description: |- + topologySpreadConstraints describe how a group of pods ought to spread across topology domains. Scheduler will + schedule pods based on the constraints. All topologySpreadConstraints are ANDed. + items: + description: TopologySpreadConstraint specifies how to spread + matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + 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. + + + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + 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. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + + + This is a beta field and requires the MinDomainsInPodTopologySpread feature gate to be enabled (enabled by default). + format: int32 + type: integer + 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. + + + 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. + + + 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 + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + replicas: + description: |- + replicas is the desired number of replicas. + A change to this setting will roll the cluster. + format: int32 + type: integer + storageClass: + description: |- + storageClass specifies the user-provided storage class. If not + configured, the default storage class is used. + properties: + name: + description: name is the storage class name. + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + tls: + description: tls specifies the TLS configuration. + properties: + autoGeneratedCerts: + description: |- + autoGeneratedCerts specifies that the certificates are auto-generated based on + the CA key pair provided. + type: boolean + directoryPathInContainer: + description: |- + directoryPathInContainer specifies the directory path in the container where + `keystore.jks`, `truststore.jks`, and `jksPassword.txt` keys are mounted. + `truststore.jks` is not configured and can be ignored when the `ignoreTrustStoreConfig` field is set to `true`. + minLength: 1 + type: string + fips: + description: |- + fips specifies the configuration of FIPS compliant Bouncy Castle type Java Keystores for the cp component's + TLS settings. TLS Secrets must have the keys keystore.bcfks, truststore.bcfks, and jksPassword.txt + properties: + enabled: + description: enabled specifies whether to enable the FIPS + configuration for cp components. + type: boolean + required: + - enabled + type: object + ignoreTrustStoreConfig: + description: |- + ignoreTrustStoreConfig indicates whether to ignore the truststore configuration + for the Confluent component. + type: boolean + jksPassword: + description: jksPassword references the secret containing the + JKS password. + properties: + secretRef: + description: |- + secretRef references the name of the secret containing the JKS password. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - secretRef + type: object + secretRef: + description: |- + secretRef references the secret containing the certificates. + More info: https://docs.confluent.io/operator/current/co-network-encryption.html#configure-user-provided-tls-certificates + maxLength: 30 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + required: + - dataVolumeCapacity + - image + - logVolumeCapacity + type: object + status: + description: status defines the observed state of the Zookeeper cluster. + properties: + arbitraryData: + description: arbitraryData is the map for any arbitrary data associated + with this Confluent component. + x-kubernetes-preserve-unknown-fields: true + authorizationType: + description: authorizationType is the authorization type for this + Confluent component. + type: string + clusterName: + description: clusterName is the name of the Confluent Platform component + cluster. + type: string + clusterNamespace: + description: clusterNamespace is the namespace where the Confluent + Platform component cluster is running. + type: string + conditions: + description: conditions specify the latest available observations + of the current state. + items: + description: Condition represent the latest available observations + of the current state. + properties: + lastProbeTime: + description: lastProbeTime shows the last time the condition + was evaluated. + format: date-time + type: string + lastTransitionTime: + description: lastTransitionTime shows the last time the condition + was transitioned from one status to another. + format: date-time + type: string + message: + description: message shows a human-readable message with details + about the transition. + type: string + reason: + description: reason shows the reason for the last transition + of the condition. + type: string + status: + description: status shows the status of the condition, one of + `True`, `False`, or `Unknown`. + type: string + type: + description: type shows the condition type. + type: string + type: object + type: array + currentReplicas: + description: currentReplicas is the number of currently running replicas. + format: int32 + type: integer + internalSecrets: + description: |- + internalSecrets are internal secrets created + by CFK for this Confluent component. + items: + type: string + type: array + internalTopicNames: + description: internalTopicNames are the topics used by the component + for internal use. + items: + type: string + type: array + myIdOffset: + description: myIdOffset shows the MyId offset configuration. + format: int32 + type: integer + observedGeneration: + description: observedGeneration is the most recent generation observed + for this Confluent component. + format: int64 + type: integer + operatorVersion: + description: operatorVersion is the internal version of CFK. + type: string + phase: + description: |- + phase describes the state of the Confluent Platform component. This can either be 'PROVISIONING' + or 'RUNNING' + 'PROVISIONING' means the Confluent Platform component is currently getting deployed and not ready yet. + 'RUNNING' means the Confluent Platform component has been successfully deployed. + type: string + readyReplicas: + description: readyReplicas is the number of currently ready replicas. + format: int32 + type: integer + replicas: + description: replicas is the number of replicas. + format: int32 + type: integer + restConfig: + description: restConfig is the REST API configuration of the Zookeeper + cluster. + properties: + advertisedExternalEndpoints: + description: advertisedExternalEndpoints specifies other advertised + endpoints used, especially for Kafka. + items: + type: string + type: array + authenticationType: + description: authenticationType shows the authentication type + configured by the listener. + type: string + externalAccessType: + description: externalAccessType shows the external access type + used for the listener. + type: string + externalEndpoint: + description: externalEndpoint specifies the external endpoint + to connect to the Confluent component cluster. + type: string + internalEndpoint: + description: internalEndpoint specifies the internal endpoint + to connect to the Confluent component cluster. + type: string + tls: + description: tls shows whether TLS is configured for the listener. + type: boolean + type: object + selector: + description: |- + selector gets the label selector of the child pod. + The Horizontal Pod Autoscaler(HPA) will scale using the label selector of the child pod. + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + scale: + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + status: {} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/NOTES.txt b/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/NOTES.txt new file mode 100644 index 000000000..13dc5b3bc --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/NOTES.txt @@ -0,0 +1,4 @@ + The Confluent Operator + +The Confluent Operator brings the component (Confluent Services) specific controllers for kubernetes by providing components specific Custom Resource +Definition (CRD) as well as managing other Confluent Platform services diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/_helpers.tpl b/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/_helpers.tpl new file mode 100644 index 000000000..2815a8374 --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/_helpers.tpl @@ -0,0 +1,42 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "confluent-operator.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 "confluent-operator.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 "confluent-operator.service-account" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "confluent-operator.name" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "confluent-operator.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/clusterrole.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/clusterrole.yaml new file mode 100644 index 000000000..c689bfb5d --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/clusterrole.yaml @@ -0,0 +1,172 @@ +{{- if .Values.rbac }} +{{- $clusterRole := or (not .Values.namespaced) (.Values.kRaftEnabled) (gt (len .Values.namespaceList) 0)}} +apiVersion: rbac.authorization.k8s.io/v1 +{{- if not $clusterRole }} +kind: Role +{{- else }} +kind: ClusterRole +{{- end }} +metadata: + labels: + app: {{ include "confluent-operator.name" . }} + app.kubernetes.io/name: {{ include "confluent-operator.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/component: "confluent-operator" + helm.sh/chart: {{ include "confluent-operator.chart" . }} + name: {{ .Values.name }} + {{- if not $clusterRole }} + namespace: {{ .Release.Namespace }} + {{- end }} +rules: +- apiGroups: + - platform.confluent.io + resources: + - '*' + verbs: + - '*' +- apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +{{- if .Values.clusterRole.openshift }} +- apiGroups: + - route.openshift.io + resources: + - routes + - routes/custom-host + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +{{- end }} +- apiGroups: + - apps + resources: + - statefulsets + - statefulsets/scale + - statefulsets/status + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - apps + resources: + - deployments + verbs: + - get +- apiGroups: + - "" + resources: + - configmaps + - persistentvolumeclaims + - persistentvolumes + - secrets + - secrets/finalizers + - pods + - services + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +{{- if gt (int (.Values.replicas)) 1 }} +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - get + - update +{{- end }} +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + - ingresses/status + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get +{{- if $clusterRole }} + - list + - watch +{{- end }} +{{- if .Values.webhooks.enabled }} +# Webhook configurations are cluster scoped +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app: {{ include "confluent-operator.name" . }} + app.kubernetes.io/name: {{ include "confluent-operator.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/component: "confluent-operator" + helm.sh/chart: {{ include "confluent-operator.chart" . }} + name: {{ .Values.name }}-webhook-{{ .Release.Namespace }} +rules: + - apiGroups: + - admissionregistration.k8s.io + resources: + - validatingwebhookconfigurations + verbs: + - get + - update + - apiGroups: + - "" + resources: + - persistentvolumes + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - list + - watch +{{- end }} +{{- end }} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/clusterrolebinding.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/clusterrolebinding.yaml new file mode 100644 index 000000000..58aa9d043 --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/clusterrolebinding.yaml @@ -0,0 +1,56 @@ +{{- if .Values.rbac }} +{{- $clusterRoleBinding := or (not .Values.namespaced) (.Values.kRaftEnabled) (gt (len .Values.namespaceList) 0)}} +{{- if not $clusterRoleBinding }} +kind: RoleBinding +{{- else }} +kind: ClusterRoleBinding +{{- end }} +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + labels: + app: {{ include "confluent-operator.name" . }} + app.kubernetes.io/name: {{ include "confluent-operator.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/component: "confluent-operator" + helm.sh/chart: {{ include "confluent-operator.chart" . }} + name: {{ .Values.name }} + {{- if not $clusterRoleBinding }} + namespace: {{ .Release.Namespace }} + {{- end }} +subjects: +- kind: ServiceAccount + name: {{ template "confluent-operator.service-account" . }} + namespace: {{ .Release.Namespace }} +roleRef: + {{- if not $clusterRoleBinding }} + kind: Role + {{- else }} + kind: ClusterRole + {{- end }} + name: {{ .Values.name }} + apiGroup: rbac.authorization.k8s.io +# Webhook configurations are cluster scoped +{{- if and (.Values.webhooks.enabled) }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app: {{ include "confluent-operator.name" . }} + app.kubernetes.io/name: {{ include "confluent-operator.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/component: "confluent-operator" + helm.sh/chart: {{ include "confluent-operator.chart" . }} + name: {{ .Values.name }}-webhook-{{ .Release.Namespace }} +subjects: + - kind: ServiceAccount + name: {{ template "confluent-operator.service-account" . }} + namespace: {{ .Release.Namespace }} +roleRef: + kind: ClusterRole + name: {{ .Values.name }}-webhook-{{ .Release.Namespace }} + apiGroup: rbac.authorization.k8s.io + {{- end }} +{{- end }} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/deployment.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/deployment.yaml new file mode 100644 index 000000000..86a64d866 --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/deployment.yaml @@ -0,0 +1,238 @@ +{{- $_ := required "Namespace is required" .Release.Namespace }} +{{- $_ := required "Name of operator is required." .Values.name }} +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: {{ include "confluent-operator.name" . }} + app.kubernetes.io/name: {{ include "confluent-operator.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/component: "confluent-operator" + helm.sh/chart: {{ include "confluent-operator.chart" . }} + version: {{ .Values.image.tag }} + name: {{ .Values.name }} + namespace: {{ .Release.Namespace }} +spec: + replicas: {{ .Values.replicas }} + selector: + matchLabels: + app.kubernetes.io/name: "confluent-operator" + app.kubernetes.io/instance: {{ .Release.Name }} + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + annotations: + {{- range $key, $value := .Values.pod.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + labels: + app: "confluent-operator" + app.kubernetes.io/name: "confluent-operator" + app.kubernetes.io/instance: {{ .Release.Name }} + confluent-platform: "true" + version: {{ .Values.image.tag }} + {{- range $key, $value := .Values.pod.labels }} + {{ $key }}: {{ $value | quote }} + {{- end }} + spec: + {{- if not (empty $.Values.affinity) }} + affinity: +{{ toYaml .Values.affinity | trim | indent 8 }} + {{- end }} + {{- if not (empty $.Values.tolerations) }} + tolerations: +{{ toYaml .Values.tolerations | trim | indent 6 }} + {{- end }} + {{- if .Values.podSecurity.enabled }} + securityContext: +{{ toYaml .Values.podSecurity.securityContext | indent 8 }} + {{- end }} + containers: + - args: + - --debug={{.Values.debug}} + - --fipsmode={{.Values.fipsmode}} + - --kraftClusterIdRecovery={{.Values.kRaftEnabled}} + {{- if gt (int (.Values.replicas)) 1 }} + - --enable-leader-election + {{- end }} + {{- if .Values.namespaced }} + {{- if empty .Values.namespaceList }} + - --namespaces={{ .Release.Namespace }} + {{- else}} + {{- $ns := "" }} + {{- range $i, $v := .Values.namespaceList }} + {{- $ns = printf "%s,%s" $ns (trim $v) }} + {{- end }} + - --namespaces={{ substr 1 (len $ns) $ns }} + {{- end }} + {{- end }} + name: {{ .Values.name }} + image: {{ .Values.image.registry }}/{{ .Values.image.repository }}:{{.Values.image.tag }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + readinessProbe: + httpGet: + port: 8080 + path: /readyz + livenessProbe: + httpGet: + port: 8080 + path: /healthz + resources: +{{ toYaml .Values.resources | trim | indent 10 }} + env: + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: NODEIP + valueFrom: + fieldRef: + fieldPath: status.hostIP + - name: DD_ENTITY_ID + valueFrom: + fieldRef: + fieldPath: metadata.uid + - name: DEPLOYMENT_NAME + value: {{ .Values.name }} + - name: CONFLUENT_SERVICE_ACCOUNT_NAME + value: {{ template "confluent-operator.service-account" . }} + {{- if .Values.numDay2Worker }} + - name: DEFAULT_DAY2_WORKER + value: "{{ .Values.numDay2Worker }}" + {{- end }} + {{- if .Values.managedCerts.enabled }} + {{- if and (empty .Values.managedCerts.caCertificate.secretRef) (empty .Values.managedCerts.caCertificate.directoryPathInContainer) }} + {{- $_ := required "secretRef or directoryPathInContainer must be configured when managedCerts is enabled" .Values.managedCerts.secretRef }} + {{- end }} + {{- if ge (.Values.managedCerts.renewBeforeInDays) (.Values.managedCerts.certDurationInDays) }} + {{- $_ := required "managedCerts.certDurationInDays for managed certs should be greater than managedCerts.renewBeforeInDays" "" }} + {{- end }} + {{- if .Values.managedCerts.certDurationInDays }} + - name: CONFLUENT_MANAGED_CERTS_DURATION_DAYS + value: "{{ .Values.managedCerts.certDurationInDays }}" + {{- end }} + {{- if .Values.managedCerts.renewBeforeInDays }} + - name: CONFLUENT_MANAGED_CERTS_RENEW_BEFORE_DAYS + value: "{{ .Values.managedCerts.renewBeforeInDays }}" + {{- end }} + {{- if .Values.managedCerts.sans }} + {{- if not (regexMatch "[ -~]" .Values.managedCerts.sans) }} + {{- $_ := required "invalid characters in managedCerts.sans. Only first 128 ASCII characters are allowed" "" }} + {{- end }} + - name: CONFLUENT_MANAGED_CERTS_SANS + value: "{{ .Values.managedCerts.sans }}" + {{- end }} + {{- if .Values.managedCerts.caCertificate.secretRef }} + - name: CONFLUENT_MANAGED_CERTS_SECRET_NAME + value: {{ .Values.managedCerts.caCertificate.secretRef }} + {{- end }} + {{- if .Values.managedCerts.caCertificate.directoryPathInContainer }} + - name: CONFLUENT_MANAGED_CERTS_DIRECTORY_PATH + value: {{ .Values.managedCerts.caCertificate.directoryPathInContainer }} + {{- end }} + {{- end }} + {{- if .Values.licenseSecretRef }} + - name: CONFLUENT_LICENSE_SECRET_NAME + value: {{ .Values.licenseSecretRef }} + {{- else if .Values.license.secretRef }} + - name: CONFLUENT_LICENSE_SECRET_NAME + value: {{ .Values.license.secretRef }} + {{- end }} + {{- if .Values.license.directoryPathInContainer }} + - name: CONFLUENT_LICENSE_DIRECTORY_PATH + value: {{ .Values.license.directoryPathInContainer }} + {{- end }} + {{- if or (.Values.telemetry.enabled) (.Values.telemetry.operator.enabled) }} + {{- if and (empty .Values.telemetry.secretRef) (empty .Values.telemetry.directoryPathInContainer) }} + {{- $_ := required "secretRef or directoryPathInContainer must be configured when telemetry is enabled" .Values.telemetry.secretRef }} + {{- end }} + - name: CP_TELEMETRY_ENABLED + value: {{ quote .Values.telemetry.enabled }} + - name: OPERATOR_TELEMETRY_ENABLED + value: {{ quote .Values.telemetry.operator.enabled }} + {{- if .Values.telemetry.secretRef }} + - name: CONFLUENT_TELEMETRY_SECRET_NAME + value: {{ .Values.telemetry.secretRef }} + {{- end }} + {{- if .Values.telemetry.directoryPathInContainer }} + - name: CONFLUENT_TELEMETRY_DIRECTORY_PATH + value: {{ .Values.telemetry.directoryPathInContainer }} + {{- end }} + {{- if .Values.telemetry.proxy.enabled }} + - name: CONFLUENT_TELEMETRY_PROXY_ENABLED + value: "true" + {{- end }} + {{- if .Values.telemetry.proxy.credentialRequired }} + - name: CONFLUENT_TELEMETRY_PROXY_CREDENTIAL_REQUIRED + value: "true" + {{- end }} + {{- end }} + {{- if .Values.webhooks.enabled }} + {{- if and (empty .Values.webhooks.tls.secretRef) (empty .Values.webhooks.tls.directoryPathInContainer) }} + {{- $_ := required "secretRef or directoryPathInContainer must be configured when webhooks are enabled" .Values.webhooks.tls.secretRef }} + {{- end }} + {{- if .Values.webhooks.tls.secretRef }} + - name: CONFLUENT_WEBHOOKS_SECRET_NAME + value: {{ .Values.webhooks.tls.secretRef }} + {{- end }} + {{- if .Values.webhooks.tls.directoryPathInContainer }} + - name: CONFLUENT_WEBHOOKS_DIRECTORY_PATH + value: {{ .Values.webhooks.tls.directoryPathInContainer }} + {{- end }} + - name: CONFLUENT_WEBHOOKS_PORT + value: {{ quote .Values.webhooks.port }} + {{- end }} + {{- if .Values.containerSecurity.enabled }} + securityContext: +{{ toYaml .Values.containerSecurity.securityContext | indent 10 }} + {{- end }} + {{- if or (not (empty .Values.mountedVolumes.volumeMounts)) (and (.Values.webhooks.enabled) (.Values.webhooks.tls.secretRef)) }} + volumeMounts: + {{- end }} + {{- if not (empty .Values.mountedVolumes.volumeMounts) }} + {{- range .Values.mountedVolumes.volumeMounts }} + {{- if and ($.Values.webhooks.enabled) (or (eq .mountPath "/mnt/sslcerts/webhook") (eq .name "webhook-certs")) }} + {{- $_ := fail "mount path \"/mnt/sslcerts/webhook\" and name \"webhook-certs\" are reserved for webhooks" }} + {{- end }} + - {{ toYaml . | indent 12 | trim }} + {{- end }} + {{- end }} + {{- if and (.Values.webhooks.enabled) (.Values.webhooks.tls.secretRef) }} + - mountPath: /mnt/sslcerts/webhook + name: webhook-certs + readOnly: true + {{- end }} + {{- if or (not (empty .Values.mountedVolumes.volumes)) (and (.Values.webhooks.enabled) (.Values.webhooks.tls.secretRef)) }} + volumes: + {{- end }} + {{- if not (empty .Values.mountedVolumes.volumes ) }} + {{- range .Values.mountedVolumes.volumes }} + {{- if and ($.Values.webhooks.enabled) (eq .name "webhook-certs") }} + {{- $_ := fail "name \"webhook-certs\" is reserved for webhooks" }} + {{- end }} + - {{ toYaml . | indent 10 | trim }} + {{- end }} + {{- end }} + {{- if and (.Values.webhooks.enabled) (.Values.webhooks.tls.secretRef) }} + - name: webhook-certs + secret: + defaultMode: 420 + secretName: {{ .Values.webhooks.tls.secretRef }} + {{- end }} + {{- if and .Values.imagePullSecretRef (not .Values.serviceAccount.create) }} + imagePullSecrets: + - name: {{ .Values.imagePullSecretRef }} + {{- end }} + serviceAccountName: {{ template "confluent-operator.service-account" . }} + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName | quote }} + {{- end }} + restartPolicy: Always + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/licensing.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/licensing.yaml new file mode 100644 index 000000000..a8ab26bcd --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/licensing.yaml @@ -0,0 +1,19 @@ +{{- if not .Values.licenseSecretRef }} +apiVersion: v1 +kind: Secret +metadata: + labels: + app: {{ include "confluent-operator.name" . }} + app.kubernetes.io/name: {{ include "confluent-operator.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/component: "confluent-operator" + helm.sh/chart: {{ include "confluent-operator.chart" . }} + namespace: {{ .Release.Namespace }} + name: confluent-operator-licensing +type: Opaque +data: + {{- if .Values.licenseKey }} + license.txt: {{ .Values.licenseKey | b64enc }} + {{- end }} +{{- end }} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/service.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/service.yaml new file mode 100644 index 000000000..2f9ecbc98 --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/service.yaml @@ -0,0 +1,28 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: {{ include "confluent-operator.name" . }} + app.kubernetes.io/name: {{ include "confluent-operator.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/component: "confluent-operator" + helm.sh/chart: {{ include "confluent-operator.chart" . }} + name: confluent-operator + namespace: {{ .Release.Namespace }} +spec: + ports: + - name: http-metric + port: 7778 + protocol: TCP + targetPort: 7778 + {{- if (.Values.webhooks.enabled) }} + - name: webhook + port: {{ .Values.webhooks.port }} + protocol: TCP + targetPort: {{ .Values.webhooks.port }} + {{- end }} + selector: + app: "confluent-operator" + app.kubernetes.io/name: "confluent-operator" + type: ClusterIP diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/serviceaccount.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/serviceaccount.yaml new file mode 100644 index 000000000..9ed5b692d --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/serviceaccount.yaml @@ -0,0 +1,18 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +{{- if .Values.imagePullSecretRef }} +imagePullSecrets: +- name: {{ .Values.imagePullSecretRef }} +{{- end }} +kind: ServiceAccount +metadata: + labels: + app: {{ include "confluent-operator.name" . }} + app.kubernetes.io/name: {{ include "confluent-operator.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/component: "confluent-operator" + helm.sh/chart: {{ include "confluent-operator.chart" . }} + name: {{ template "confluent-operator.service-account" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/validatingwebhookconfiguration.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/validatingwebhookconfiguration.yaml new file mode 100644 index 000000000..e7235f9fd --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/templates/validatingwebhookconfiguration.yaml @@ -0,0 +1,184 @@ +{{- if (.Values.webhooks.enabled) }} +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + labels: + app: {{ include "confluent-operator.name" . }} + app.kubernetes.io/name: {{ include "confluent-operator.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/component: "confluent-operator" + helm.sh/chart: {{ include "confluent-operator.chart" . }} + name: confluent-operator-{{ .Release.Namespace }}.webhook.platform.confluent.io +webhooks: +- admissionReviewVersions: + - v1beta1 + clientConfig: + service: + name: confluent-operator + namespace: {{ .Release.Namespace }} + path: /confluent-operator/validate + port: {{ .Values.webhooks.port }} + failurePolicy: Fail + name: cfk-resources.webhooks.platform.confluent.io + namespaceSelector: + matchExpressions: + - key: confluent-operator.webhooks.platform.confluent.io/disable + operator: NotIn + values: [ "true" ] + {{- if .Values.namespaced }} + - key: kubernetes.io/metadata.name + operator: In + values: + {{- if empty .Values.namespaceList }} + - {{ .Release.Namespace }} + {{- else }} + {{- range $i, $v := .Values.namespaceList }} + - {{ trim $v }} + {{- end }} + {{- end }} + {{- end }} + objectSelector: + matchExpressions: + - key: confluent-operator.webhooks.platform.confluent.io/disable + operator: NotIn + values: [ "true" ] + rules: + - apiGroups: + - platform.confluent.io + apiVersions: + - v1beta1 + operations: + - UPDATE + - DELETE + resources: + - zookeepers + - kafkas + - kraftcontrollers + - ksqldbs + - controlcenters + scope: Namespaced + sideEffects: None + timeoutSeconds: 10 +- admissionReviewVersions: + - v1beta1 + clientConfig: + service: + name: confluent-operator + namespace: {{ .Release.Namespace }} + path: /confluent-operator/validate + port: {{ .Values.webhooks.port }} + failurePolicy: Fail + name: core-resources.webhooks.platform.confluent.io + namespaceSelector: + matchExpressions: + - key: confluent-operator.webhooks.platform.confluent.io/disable + operator: NotIn + values: [ "true" ] + {{- if .Values.namespaced }} + - key: kubernetes.io/metadata.name + operator: In + values: + {{- if empty .Values.namespaceList }} + - {{ .Release.Namespace }} + {{- else }} + {{- range $i, $v := .Values.namespaceList }} + - {{ trim $v }} + {{- end }} + {{- end }} + {{- end }} + objectSelector: + matchLabels: + confluent-platform: "true" + rules: + - apiGroups: + - apps + apiVersions: + - v1 + operations: + - DELETE + resources: + - statefulsets + scope: Namespaced + sideEffects: None + timeoutSeconds: 10 +- admissionReviewVersions: + - v1beta1 + clientConfig: + service: + name: confluent-operator + namespace: {{ .Release.Namespace }} + path: /confluent-operator/validate + port: {{ .Values.webhooks.port }} + failurePolicy: Fail + name: kafka-pods.webhooks.platform.confluent.io + namespaceSelector: + matchExpressions: + - key: confluent-operator.webhooks.platform.confluent.io/disable + operator: NotIn + values: [ "true" ] + {{- if .Values.namespaced }} + - key: kubernetes.io/metadata.name + operator: In + values: + {{- if empty .Values.namespaceList }} + - {{ .Release.Namespace }} + {{- else }} + {{- range $i, $v := .Values.namespaceList }} + - {{ trim $v }} + {{- end }} + {{- end }} + {{- end }} + objectSelector: + matchLabels: + confluent-platform: "true" + platform.confluent.io/type: kafka + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - DELETE + resources: + - pods + scope: Namespaced + sideEffects: None + timeoutSeconds: 30 +- admissionReviewVersions: + - v1beta1 + clientConfig: + service: + name: confluent-operator + namespace: {{ .Release.Namespace }} + path: /confluent-operator/validate + port: {{ .Values.webhooks.port }} + failurePolicy: Fail + name: evictions.webhooks.platform.confluent.io + namespaceSelector: + matchExpressions: + {{- if .Values.namespaced }} + - key: kubernetes.io/metadata.name + operator: In + values: + {{- if empty .Values.namespaceList }} + - {{ .Release.Namespace }} + {{- else }} + {{- range $i, $v := .Values.namespaceList }} + - {{ trim $v }} + {{- end }} + {{- end }} + {{- end }} + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + resources: + - pods/eviction + scope: Namespaced + sideEffects: None + timeoutSeconds: 30 +{{- end }} diff --git a/charts/confluent/confluent-for-kubernetes/0.1033.3/values.yaml b/charts/confluent/confluent-for-kubernetes/0.1033.3/values.yaml new file mode 100644 index 000000000..16bf6ba5b --- /dev/null +++ b/charts/confluent/confluent-for-kubernetes/0.1033.3/values.yaml @@ -0,0 +1,269 @@ +## Confluent operator name +## +name: confluent-operator +## +## license Key +## +licenseKey: "" +## +## Load license either from the secret or through directoryPath. +## This will take precedence over licenseKey field. +## +license: + ## + ## The license secret reference name is injected through + ## CONFLUENT_LICENSE_SECRET_NAME environment variable. + ## The expected key: license.txt. license.txt contains raw license data. + ## For backward compatibility, licenseSecretRef field takes precedence if configured. + secretRef: "" + ## The directoryPathInContainer value is injected through + ## CONFLUENT_LICENSE_DIRECTORY_PATH environment variable. + ## The expected key: license.txt. license.txt file must have value in pattern `license=`. + ## + ## This configuration takes precedence over license.secretRef or licenseSecretRef field. + ## + directoryPathInContainer: "" + +## +## AutoGenerated certificates configuration. +## We will continue using older model of reading CA from secret "ca-pair-sslcerts" unless +## managedCerts.enabled is set to true. +## +managedCerts: + ## + ## Denotes whether CFK managed certs are configured with helm values. If this is set to true + ## values below will be used for auto-generated certificates and will cause a cluster roll + ## first time after this is enabled. + ## + enabled: false + ## + ## CA certificate pair for AutoGenerated certificates in this CFK operator deployment. + ## + caCertificate: + ## + ## CA pair secret reference name is injected through + ## CONFLUENT_MANAGED_CERTS_SECRET_NAME environment variable. + ## The expected keys are tls.crt and tls.key for CA Certificate and CA Certificate Key + ## respectively. + ## + secretRef: "" + ## The directoryPathInContainer value for CA pair certificates are injected through + ## CONFLUENT_MANAGED_CERTS_DIRECTORY_PATH environment variable. + ## The expected files are tls.crt and tls.key for CA Certificate and CA Certificate Key + ## respectively. + ## + directoryPathInContainer: "" + ## + ## Validity for Auto-generated certificates is injected through + ## CONFLUENT_MANAGED_CERTS_DURATION_DAYS environment variable. + ## + certDurationInDays: 60 + ## + ## Renewal time for Auto-generated certificates is injected through + ## CONFLUENT_MANAGED_CERTS_RENEW_BEFORE_DAYS environment variable. + ## + renewBeforeInDays: 30 + ## + ## SANs to be added for all auto-generated certificates generated by this + ## CFK operator. This is injected through CONFLUENT_MANAGED_CERTS_SANS + ## environment variable. + ## Use this for adding wild card SANs. Modifying this will trigger regeneration of + ## certs for all CP clusters managed by the CFK operator. + ## + sans: "" + +### +## Image pull secret +imagePullSecretRef: confluent-registry +## Confluent Operator Image Information +## +image: + registry: docker.io + repository: confluentinc/confluent-operator + pullPolicy: IfNotPresent + tag: "0.1033.3" + +### +## Priority class for Confluent Operator pod +priorityClassName: "" +## Number of pods for Operator +## Enables leader election if more than one replica +replicas: 1 +## Confluent Operator Cluster Access +## If true, operator only creates roles/rolebinding for the release namespace +## Otherwise, it has cluster access with clusterrole/clusterrrolebinding +namespaced: true +### list of namespaces to watch by operator +### This field only takes in effect if `namespaced=true`. By default, it will only watch the release namespace +### Otherwise, it will watch specified namespaces. If watching only release namespace, do not specify this field +namespaceList: [] +## Confluent Operator Pod Resources +## +resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 256Mi +## Pod termination grace-period +## +terminationGracePeriodSeconds: 30 +## Enable debugging +## +debug: false +## Enable Fips Mode +## +fipsmode: false +## Set number of day2 workers +## +numDay2Worker: "" +## +## Configure affinity, +## More information here https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ +## +affinity: {} +## Example for nodeAffinity, configure as required. +##affinity: +## nodeAffinity: +## requiredDuringSchedulingIgnoredDuringExecution: +## nodeSelectorTerms: +## - matchExpressions: +## - key: "node-role.kubernetes.io/compute" +## operator: In +## values: +## - "true" + +## +## Configure tolerations +## https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +## +tolerations: [] +## +##tolerations: +##- key: "dedicated" +## operator: "Equal" +## value: "operator" +## effect: "NoSchedule" + +## Pod Security Context +## +podSecurity: + enabled: true + securityContext: + fsGroup: 1001 + runAsUser: 1001 + runAsNonRoot: true + +## Container Security Context +## Container security context overrides security context defined at pod level. +## For example following container security context would override the +## default PodSecurityContext defined above +## +## securityContext: +## runAsUser: 2001 +## runAsNonRoot: false +## +## Refer to this documentation on how configure security context for container +## https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-containerh +## +containerSecurity: + enabled: false + securityContext: {} + +## +## ServiceAccount +## If enabled it will create, otherwise it will +## not create +## +serviceAccount: + create: true + name: "" +## Enable Kubernetes RBAC +## When set to true, it will create a proper role/rolebinding or cluster/clusterrolebinding based on namespaced field. +## If a user doesn't have permission to create role/rolebinding then they can disable rbac field and +## create required resources out of band to be used by the Operator. In this case, follow the +## templates/clusterrole.yaml and templates/clusterrolebiding.yaml to create proper required resources. +rbac: true + +## Enable extra Kubernetes API groups in role/clusterrole resource +## When set to true, it will add apiGroups to role/clusterrole for OpenShift route resource +clusterRole: + openshift: true + +### +### Confluent Telemetry Report configuration +## The secretRef contains following data, +## telemetry.txt: |- +## api.key= +## api.secret= +## proxy.url= # only required if proxy is enabled +## proxy.username= # only required if proxy requires credential +## proxy.password= +## +telemetry: + operator: + enabled: false + enabled: false + proxy: + enabled: false + credentialRequired: false + secretRef: "" + ## To use directoryPathInContainer, need to make sure + ## you mount telemetry.txt in the path you provided here in each pod + directoryPathInContainer: "" + +## In case of KRaft, we need to preserve the KRaft ClusterID in PV annotation +## for disaster recovery case. Enabling this ensures we create proper ClusterRoles +## to be able to set this annotation in PersistentVolumes. +kRaftEnabled: false + +### +### Webhooks configuration +## To enable webhooks, it requires TLS certificates to set up webhook server, +## which used for secure communication between webhook server and kubernetes api server. +## Please provide the TLS keys and certificates with format as mentioned in this doc: +## https://docs.confluent.io/operator/current/co-network-encryption.html#provide-tls-keys-and-certificates-in-pem-format. +## The certificate must have the Subject Alternative Name (SAN) of the form: confluent-operator..svc +webhooks: + enabled: false + port: 8443 + tls: + secretRef: "" + directoryPathInContainer: "" + +## +## Pod annotations/labels configurations +## +pod: + annotations: + prometheus.io/path: "/metrics" + prometheus.io/port: "7778" + prometheus.io/scrape: "true" + labels: {} +# labels: +# key: "value" + +## +## Load license from the secret reference +## +Deprecated, use license.secretRef instead. +## +licenseSecretRef: "" + +## +## Volumes to mount on CFK operator +## Refer to the Kubernetes volume/volumeMounts format: https://kubernetes.io/docs/concepts/storage/volumes/ +## +## Example with a PVC. +## mountedVolumes: +## volumes: +## - name: custom-volume +## persistentVolumeClaim: +## claimName: pvc-test +## volumeMounts: +## - name: custom-volume +## mountPath: /mnt/ +## +mountedVolumes: + volumes: [] + volumeMounts: [] diff --git a/charts/jenkins/jenkins/5.5.0/CHANGELOG.md b/charts/jenkins/jenkins/5.5.0/CHANGELOG.md new file mode 100644 index 000000000..96e9af0c7 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/CHANGELOG.md @@ -0,0 +1,3041 @@ +# Changelog + +This file documents all notable changes to the Jenkins Helm Chart. +The release numbering uses [semantic versioning](http://semver.org). + +Use the following links to reference issues, PRs, and commits prior to v2.6.0. + +* Issue: `https://github.com/helm/charts/issues/[issue#]` +* PR: `https://github.com/helm/charts/pull/[pr#]` +* Commit: `https://github.com/helm/charts/commit/[commit]/stable/jenkins` + +The changelog until v1.5.7 was auto-generated based on git commits. +Those entries include a reference to the git commit to be able to get more details. + +## 5.5.0 + +Introduce capability of set skipTlsVerify and usageRestricted flags in additionalClouds + + +## 5.4.4 + +Update CHANGELOG.md, README.md, and UPGRADING.md for linting + +## 5.4.3 + +Update `configuration-as-code` to version `1836.vccda_4a_122a_a_e` + +## 5.4.2 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `1.27.5` + +## 5.4.1 + +Update `jenkins/jenkins` to version `2.452.3` + +## 5.4.0 + +Introduce capability of additional mountPaths and logging file paths for config reload container + +## 5.3.6 + +Update `workflow-aggregator` to version `600.vb_57cdd26fdd7` + +## 5.3.5 + +Update `kubernetes` to version `4253.v7700d91739e5` + +## 5.3.4 + +Update `jenkins/jenkins` to version `2.452.3-jdk17` +## 5.3.3 + +Update `jenkins/inbound-agent` to version `3256.v88a_f6e922152-1` + +## 5.3.2 + +Update `kubernetes` to version `4248.vfa_9517757b_b_a_` + +## 5.3.1 + +Fix Tiltfile deprecated value reference + +## 5.3.0 + +Add `controller.topologySpreadConstraints` + +## 5.2.2 + +Update `kubernetes` to version `4246.v5a_12b_1fe120e` + +## 5.2.1 + +Update `jenkins/jenkins` to version `2.452.2-jdk17` + +## 5.2.0 + +Add `agent.inheritYamlMergeStrategy` to allow configuring this setting on the default agent pod template. + +## 5.1.31 + +Update `kubernetes` to version `4245.vf5b_83f1fee6e` + +## 5.1.30 + +Add `controller.JCasC.configMapAnnotations` to allow setting annotations on the JCasC ConfigMaps. + +## 5.1.29 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `1.27.4` + +## 5.1.28 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `1.27.3` + +## 5.1.27 + +Update `kubernetes` to version `4244.v4fb_b_00994a_90` + +## 5.1.26 + +Update `kubernetes` to version `4238.v41b_3ef14a_5d8` + +## 5.1.25 + +Update `kubernetes` to version `4236.vc06f753c3234` + +## 5.1.24 + +Update `kubernetes` to version `4234.vdf3e78112369` + +## 5.1.23 + +Update `kubernetes` to version `4233.vb_67a_0e11a_039` + +## 5.1.22 + +Update `configuration-as-code` to version `1810.v9b_c30a_249a_4c` + +## 5.1.21 + +Update `kubernetes` to version `4231.vb_a_6b_8936497d` + +## 5.1.20 + +Update `kubernetes` to version `4230.vceef11cb_ca_37` + +## 5.1.19 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `1.27.2` + +## 5.1.18 + +Update `configuration-as-code` to version `1807.v0175eda_00a_20` + +## 5.1.17 + +Update `jenkins/inbound-agent` to version `3248.v65ecb_254c298-1` + +## 5.1.16 + +Update `configuration-as-code` to version `1805.v1455f39c04cf` + +## 5.1.15 + +Update `jenkins/jenkins` to version `2.452.1-jdk17` + +## 5.1.14 + +Update `kubernetes` to version `4219.v40ff98cfb_d6f` + +## 5.1.13 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `1.27.1` + +## 5.1.12 + +Update `git` to version `5.2.2` + +## 5.1.11 + +Update `kubernetes` to version `4214.vf10083a_42e70` + +## 5.1.10 + +Update `kubernetes` to version `4211.v08850dd0dfa_3` + +## 5.1.9 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `1.26.2` + +## 5.1.8 + +Update `kubernetes` to version `4209.vc646b_71e5269` + +## 5.1.7 + +Update `kubernetes` to version `4208.v4017b_a_27a_d67` + +## 5.1.6 + +Update `jenkins/jenkins` to version `2.440.3-jdk17` + +## 5.1.5 + +Fix Prometheus controller name. + +## 5.1.4 + +Update `docker.io/bats/bats` to version `1.11.0` + +## 5.1.3 + +Update `jenkins/jenkins` to version `2.440.2-jdk17` + +## 5.1.2 + +Update `kubernetes` to version `4203.v1dd44f5b_1cf9` + +## 5.1.1 + +Update `kubernetes` to version `4199.va_1647c280eb_2` + +## 5.1.0 + +Add `agent.restrictedPssSecurityContext` to automatically inject in the jnlp container a securityContext that is suitable for the use of the restricted Pod Security Standard + +## 5.0.20 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `1.26.1` + +## 5.0.19 + +Introduced helm-docs to automatically generate `values.yaml` documentation. + +## 5.0.18 + +Update `kubernetes` to version `4193.vded98e56cc25` + +## 5.0.17 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `1.26.0` + +## 5.0.16 + +Enable support for deleting plugin configuration files at startup. + +## 5.0.15 + +Fixed changelog entries for previous version bumps + + +## 5.0.14 + +Update `jenkins/jenkins` to version `2.440.1-jdk17` + +## 5.0.13 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `1.25.4` + +## 5.0.12 + +Fix controller.sidecars.additionalSidecarContainers renaming and add tests + +## 5.0.11 + +* Add controller.sidecars.configAutoReload.scheme to specify protocol scheme when connecting Jenkins configuration-as-code reload endpoint +* Add controller.sidecars.configAutoReload.skipTlsVerify to force the k8s-sidecar container to skip TLS verification when connecting to an HTTPS Jenkins configuration-as-code reload endpoint + +## 5.0.10 + +Update `jenkins/inbound-agent` to version `3206.vb_15dcf73f6a_9-3` + +## 5.0.9 + +Update `kubernetes` to version `4186.v1d804571d5d4` + +## 5.0.8 + +Update `configuration-as-code` to version `1775.v810dc950b_514` + +## 5.0.7 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `docker.io/kiwigrid/k8s-sidecar` + +## 5.0.6 + +Removed `docker.io` prefix from inbound-agent image + +## 5.0.5 + +Prefixed artifacthub.io/images with `docker.io` + +## 5.0.4 + +Updated super-linter to v6. Updated README.md and CHANGELOG.md to fix linting issues. + +## 5.0.2 + +Update `git` to version `5.2.1` + +## 5.0.1 + +Update `docker.io/bats/bats` to version `v1.10.0` + +## 5.0.0 + + > [!CAUTION] + > Several fields have been renamed or removed. See [UPGRADING.md](./UPGRADING.md#to-500) + +The Helm Chart is now updated automatically via [Renovate](https://docs.renovatebot.com/) + +## 4.12.1 + +Update Jenkins image and appVersion to jenkins lts release version 2.426.3 + +## 4.12.0 + +Add support for [generic ephemeral storage](https://github.com/jenkinsci/kubernetes-plugin/pull/1489) in `agent.volumes` and `agents.workspaceVolume`. + +| plugin | old version | new version | +|------------|---------------------|--------------------| +| kubernetes | 4029.v5712230ccb_f8 | 4174.v4230d0ccd951 | + +## 4.11.2 + +Fixed documentation for controller.initScripts. + +## 4.11.1 + +Updated helm-unittest and made unittests compatible. + +## 4.11.0 + +Add multi-cloud support. + +## 4.10.0 + +Bumped Jenkins inbound agent from 3107.v665000b_51092-15 to 3192.v713e3b_039fb_e-5. + +## 4.9.2 + +Update Jenkins image and appVersion to jenkins lts release version 2.426.2 + + +Notes about [Artifact Hub](https://artifacthub.io/packages/helm/jenkinsci/jenkins?modal=changelog) changelog processing: +- Remove empty lines +- Keep only ASCII characters (no emojis) +- One change per line +- Remove table(s) (lines starting by "|") +- Backticks aren't rendered on artifacthub.io changelog + +## 4.9.1 + +Restore artifact hub notes location in CHANGELOG.md + +## 4.9.0 + +Update base images from JDK 11 to JDK 17. + +## 4.8.6 + +Proper `artifacthub.io/changes` changelog annotation preprocessing. + +## 4.8.5 + +Fix `artifacthub.io/changes` changelog annotation added to the released chart. + +## 4.8.4 + +Add `artifacthub.io/changes` changelog annotation to the released chart. + +## 4.8.3 + +Update Jenkins image and appVersion to jenkins lts release version 2.426.1 + +## 4.8.2 + +Add the ability to modify `retentionTimeout` and `waitForPodSec` default value in JCasC + +## 4.8.1 + +Reintroduces changes from 4.7.0 (reverted in 4.7.1), with additional fixes: + +- METHOD is now allowed in `env` and is not duplicated anymore +- No calls to JCasC reload endpoint from the init container + +## 4.8.0 + +Adds support for ephemeralStorage request and limit in Kubernetes plugin JCasC template + +## 4.7.4 + +Add the config-init-script checksum into the controller statefullset pod annotations to trigger restart of the pod in case of updated init scripts. + +## 4.7.3 + +Update Jenkins image and appVersion to jenkins lts release version 2.414.3 + +## 4.7.1 + +Changes in 4.7.0 were reverted. + +## 4.7.0 + +Runs `config-reload` as an init container, in addition to the sidecar container, to ensure that JCasC YAMLs are present before the main Jenkins container starts. This should fix some race conditions and crashes on startup. + +## 4.6.7 + +Change jenkins-test image label to match the other jenkins images + +## 4.6.5 + +Update Jenkins image and appVersion to jenkins lts release version 2.414.2 + +## 4.6.4 + +Introducing TPL function on variables related to hostname in `./charts/jenkins/templates/jenkins-controller-ingress.yaml` + +## 4.6.3 + +Add values to documentation + +## 4.6.2 + +Update word from hundreds to over 1800 to align with blurb at . + +## 4.6.1 + +Update `configuration-as-code` plugin to fix dependency issues with `azure-ad` plugin + +## 4.6.0 + +Added `.Values.controller.httpsKeyStore.jenkinsHttpsJksSecretKey` to allow overriding the default secret key containing the JKS file. +Added `.Values.controller.httpsKeyStore.jenkinsHttpsJksPasswordSecretName` to allow getting the JKS password from a different secret. +Added `.Values.controller.httpsKeyStore.jenkinsHttpsJksPasswordSecretKey` to allow overriding the default secret key containing the JKS password. + +## 4.5.1 + +Update Jenkins image and appVersion to jenkins lts release version 2.414.1 + + +## 4.5.0 + +Added `.Values.persistence.dataSource` to allow cloning home PVC from existing dataSource. + +## 4.4.2 + +Update Jenkins image and appVersion to jenkins lts release version 2.401.3 + + +## 4.4.1 + +Added `.Values.agent.jnlpregistry` to allow agents to be configured with private registry. + +## 4.4.0 + +Add config keys for liveness probes on agent containers. + + +## 4.3.30 + +Update Jenkins version in controller test matching LTS version + +## 4.3.29 + +Update Jenkins image and appVersion to jenkins lts release version 2.401.2 + + +## 4.3.28 + +Allow the kubernetes API server URL to be configurable. + +## 4.3.27 + +Bump kiwigrid/k8s-sidecar from 1.23.1 to 1.24.4 and jenkins/inbound-agent from 3107.v665000b_51092-5 to 3107.v665000b_51092-15. + +## 4.3.26 + +Fix various typos in the chart documentation. + +## 4.3.25 + +| plugin | old version | new version | +|-----------------------|----------------------|-----------------------| +| kubernetes | 3900.va_dce992317b_4 | 3937.vd7b_82db_e347b_ | +| configuration-as-code | 1625.v27444588cc3d | 1647.ve39ca_b_829b_42 | +| git | 5.0.0 | 5.1.0 | +| ldap | 671.v2a_9192a_7419d | 682.v7b_544c9d1512 | + +## 4.3.24 + +Update Jenkins image and appVersion to jenkins lts release version 2.401.1 + + +## 4.3.23 + +Update Jenkins image and appVersion to jenkins lts release version 2.387.3 + + +## 4.3.22 + + +Bump chart version. + +## 4.3.21 + + +Document building charts for weekly releases. + +## 4.3.20 + + +Enhance repository appearance and miscellaneous cleanup. + +## 4.3.19 + + +Comply with superlinter rules and address ShellCheck issues. + +## 4.3.18 + + +Bump kiwigrid/k8s-sidecar from 1.15.0 to 1.23.1. + +## 4.3.17 + + +Bump jenkins/inbound-agent from 4.11.2-4 to 3107.v665000b_51092-5. + +## 4.3.16 + + +Update bundled plugins: +- [ldap](https://plugins.jenkins.io/ldap/): From 2.5 to 671.v2a_9192a_7419d +- [kubernetes](https://plugins.jenkins.io/kubernetes/): From 3734.v562b_b_a_627ea_c to 3900.va_dce992317b_4 +- [workflow-aggregator](https://plugins.jenkins.io/workflow-aggregator/): From 590.v6a_d052e5a_a_b_5 to 590.v6a_d052e5a_a_b_5 +- [configuration-as-code](https://plugins.jenkins.io/configuration-as-code/): From 1569.vb_72405b_80249 to 1625.v27444588cc3d + +## 4.3.15 + + +Update bats from 1.2.1 to 1.9.0. + +## 4.3.14 + + +Update various GH actions, typo fixes, and miscellaneous chores. + +## 4.3.13 + + +Bump helm-unittest from 0.2.8 to 0.2.11. + +## 4.3.12 + + +Update wording in values.yml. + +## 4.3.11 + +Update Jenkins image and appVersion to jenkins lts release version 2.387.2 + + +## 4.3.10 + +Correct incorrect env var definition +Disable volume mount if disableSecretMount enabled + +## 4.3.9 + +Document `.Values.agent.directConnection` in readme. +Add default value for `.Values.agent.directConnection` to `values.yaml` + +## 4.3.8 + +Added `.Values.agent.directConnection` to allow agents to be configured to connect direct to the JNLP port on the +controller, preventing the need for an external HTTP endpoint for this purpose. + +## 4.3.7 + +Added `.Values.controller.shareProcessNamespace` and `.Values.controller.httpsKeyStore.disableSecretMount` to enable sourcing TLS certs from external issuers + +## 4.3.6 + +Update Jenkins image and appVersion to jenkins lts release version 2.387.1 + +## 4.3.5 + +Added `.Values.helmtest.bats.image` and `.Values.helmtest.bats.image` to allow unit tests to be configurable. Fixes [https://github.com/jenkinsci/helm-charts/issues/683] + +## 4.3.4 + +Update Jenkins image and appVersion to jenkins lts release version 2.375.3 + + +## 4.3.3 + +Removed hardcoding of chart version in tests to make maintenance easier + +## 4.3.2 + +Added `.Values.serviceAccount.extraLabels` on Service Account +Added `.Values.serviceAccountAgent.extraLabels` on Agent's Service Account + + +## 4.3.0 + +Moved use of `.Values.containerEnv` within `jenkins` Container to top of `env` block to allow for subsequent Environment Variables to reference these additional ones. + +## 4.2.21 + +Update Jenkins image and appVersion to jenkins lts release version 2.375.2 + + +## 4.2.20 + +Fixed the `controller.prometheus.metricRelabelings` being unable to convert the value to the ServiceMonitor. +Added `controller.prometheus.relabelings` to allow relabling before scrape. +Added default values for `controller.prometheus.relabelings` and `controller.prometheus.metricRelabelings`. + +## 4.2.19 + +CronJob API version upgraded to batch/v1 + +## 4.2.18 + +Added option to set secretEnvVars. + +## 4.2.17 + +Update Jenkins image and appVersion to jenkins lts release version 2.375.1 + + +## 4.2.16 + +Fixed chart notes not rendering Jenkins URL with prefix when `controller.jenkinsUriPrefix` is set. +Fixed chart notes not rendering Jenkins URL with `https` when `controller.ingress.tls` or `controller.controller.httpsKeyStore.enable` is set. +Fixed chart notes rendering wrong JCasC URL when not using `controller.ingress`. + +## 4.2.15 + +Update Jenkins image and appVersion to jenkins lts release version 2.361.4 + +## 4.2.14 + +Added option to mount all keys from an existing k8s secret + +## 4.2.13 + +Adding `tpl` to `controller.additionalExistingSecrets` + +## 4.2.12 + +Update Jenkins image and appVersion to jenkins lts release version 2.361.3 + + +## 4.2.11 + +Update default plugin versions + +| plugin | old version | new version | +|-----------------------|-----------------------|------------------------| +| kubernetes | 3706.vdfb_d599579f3 | 3734.v562b_b_a_627ea_c | +| git | 4.11.5 | 4.13.0 | +| configuration-as-code | 1512.vb_79d418d5fc8 | 1569.vb_72405b_80249 | + +## 4.2.10 +Fix grammar and typos + +## 4.2.9 +Update Jenkins image and appVersion to jenkins lts release version 2.361.2 + +## 4.2.8 +Modify the condition to trigger copying jenkins_config files when configAutoReload option is disabled during Jenkins initialization + +## 4.2.7 +Support for remote URL for configuration + +## 4.2.6 +Add option to set hostnetwork for agents + +## 4.2.5 +Add an extra optional argument to extraPorts in order to specify targetPort + +## 4.2.4 +Remove k8s capibility requirements when setting priority class for controller + +## 4.2.3 Update plugin versions + +| plugin | old version | new version | +| --------------------- | --------------------- | --------------------- | +| kubernetes | 3600.v144b_cd192ca_a_ | 3706.vdfb_d599579f3 | +| workflow-aggregator | 581.v0c46fa_697ffd | 590.v6a_d052e5a_a_b_5 | +| configuration-as-code | 1429.v09b_044a_c93de | 1512.vb_79d418d5fc8 | +| git | 4.11.3 | 4.11.5 | + +Resolve version conflict between default install of plugins. + +## 4.2.2 + +Support Google Managed Prometheus + +## 4.2.1 + +Remove option to provide command and args of agent as YAML. This feature was never supported by the Jenkins Kubernetes +plugin. + +## 4.2.0 + +Add option to provide additional containers to agents + +## 4.1.18 + +Update Jenkins image and appVersion to jenkins lts release version 2.361.1 + + +## 4.1.17 + +Update Jenkins casc default settings to allow `security` configs to be provided + + +## 4.1.16 + +Update Jenkins image and appVersion to jenkins lts release version 2.346.3 + + +## 4.1.15 + +`projectNamingStrategy` is configurable in default config. + +## 4.1.14 + +If `installPlugins` is disabled, don't create unused plugins volume. + +## 4.1.13 + +Update Jenkins image and appVersion to jenkins lts release version 2.346.2 + + +## 4.1.12 + +If keystore is defined, it is now also made available in the initContainer. + +## 4.1.11 + +JCasC ConfigMaps now generate their name from the `jenkins.casc.configName` helper + +## 4.1.10 + +Update Jenkins image and appVersion to jenkins lts release version 2.346.1 + + +## 4.1.9 + +Allow setting `imagePullSecret` for backup job via `backup.imagePullSecretName` + +## 4.1.8 + +Fix path of projected secrets from `additionalExistingSecrets`. + +## 4.1.7 + +Update readme with explanation on the required environmental variable `AWS_REGION` in case of using an S3 bucket. + +## 4.1.6 + +project adminSecret, additionalSecrets and additionalExistingSecrets instead of mount with subPath + +## 4.1.5 + +Update readme to fix `JAVA_OPTS` name. + +## 4.1.4 +Update plugins + +## 4.1.3 +Update jenkins-controller-statefulset projected volumes definition + +## 4.1.1 +Added 'controller.prometheus.metricRelabelings' to allow relabling and dropping unused prometheus metrics + +## 4.1.0 + +Added `controller.sidecars.configAutoReload.envFrom`, `controller.initContainerEnvFrom`, `controller.containerEnvFrom` + +## 4.0.1 + +No code changes - CI updated to run unit tests using Helm 3.8.2. + +## 4.0.0 + +Removes automatic `remotingSecurity` setting when using a container tag older than `2.326` (introduced in [`3.11.7`](#3117)). If you're using a version older than `2.326`, you should explicitly set `.controller.legacyRemotingSecurityEnabled` to `true`. + +## 3.12.2 + +Update Jenkins image and appVersion to jenkins lts release version 2.332.3 + +## 3.12.1 + +Make namespace configurable for agents and additional agents. + +## 3.12.0 + +Added a flag for disabling the default Jenkins Agent configuration. + +## 3.11.10 + +Update Jenkins image and appVersion to jenkins lts release version 2.332.2 + +## 3.11.9 Bump configuration-as-code plugin version + +| plugin | old version | new version | +| --------------------- | ----------- | ----------- | +| configuration-as-code | 1.51 | 1414.v878271fc496f | + +## 3.11.8 + +Make [externalTrafficPolicy](https://kubernetes.io/docs/concepts/services-networking/service/#traffic-policies) and `loadBalancerSourceRanges` fields customizable for Agent listener service via `controller.agentListenerExternalTrafficPolicy` and `controller.loadBalancerSourceRanges`. + +## 3.11.7 + +Removed Configuration as Code `remotingSecurity` section for Jenkins 2.326 or newer. See [Documentation](https://www.jenkins.io/redirect/AdminWhitelistRule) to learn more. + +## 3.11.6 + +Update Jenkins image and appVersion to jenkins lts release version 2.332.1 + + +## 3.11.5 + +Change Backup Role name function call to match the RoleDef function call in the Backup RoleBinding + +## 3.11.4 + +Update Jenkins image and appVersion to jenkins lts release version 2.319.3 + + +## 3.11.3 + +Update kiwigrid/k8s-sidecar:1.15.0 +Update jenkins/inbound-agent:4.11.2-4 + +## 3.11.2 + +Improve example for workspaceVolume. Clarify that this is not a list. + +## 3.11.1 + +Update configuration-as-code plugin to 1.55.1 + + +## 3.11.0 + +Update default plugin versions + +| plugin | old version | new version | +| --------------------- | ----------- | ----------- | +| kubernetes | 1.31.1 | 1.31.3 | +| git | 4.10.1 | 4.10.2 | + +## 3.10.3 + +Update Jenkins image and appVersion to jenkins lts release version 2.319.2 + + +## 3.10.2 + +Fix definition of startupProbe when deploying on a Kubernetes cluster < 1.16 + +## 3.10.1 + +correct VALUES_SUMMARY.md for installLatestPlugins + +## 3.10.0 + +Update default plugin versions + +| plugin | old version | new version | +| --------------------- | ----------- | ----------- | +| kubernetes | 1.30.11 | 1.31.1 | +| git | 4.10.0 | 4.10.1 | +| configuration-as-code | 1.54 | 1.55 | + +## 3.9.4 + +Add JAVA_OPTIONS to the readme so proxy settings get picked by jenkins-plugin-cli + +## 3.9.3 + +Fix config reload request URL when httpsKeystore in use + +## 3.9.2 + +Update Jenkins image and appVersion to jenkins lts release version 2.319.1 +Update following plugins: + +* kubernetes:1.30.11 +* git:4.10.0 +* configuration-as-code:1.54 + +## 3.9.1 + +Adding `tpl` to `controller.overrideArgs` + +## 3.9.0 + +Added containerSecurityContext + +## 3.8.9 + +Fix mounting of HTTPS keystore secret when httpsKeyStore is enabled + +## 3.8.8 + +Update Jenkins image and appVersion to jenkins lts release version 2.303.3 + +## 3.8.7 + +Adding `tpl` to `initScripts` + +## 3.8.6 + +Add `controller.tagLabel` to specify the label for the image tag, for example `jdk11` or `alpine` + +## 3.8.5 + +Move jenkins web root outside of home dir + +## 3.8.4 + +Add `controller.initConfigMap` to pass pre-existing `init.groovy.d` ConfigMaps to the controller + +## 3.8.3 + +Update missed reference to jenkins/inbound-agent:4.11-1 + +## 3.8.2 + +Update jenkins/inbound-agent:4.11-1 + +## 3.8.1 + +Update jenkins/inbound-agent:4.10-3 + +## 3.8.0 + +Update kiwigrid/k8s-sidecar:1.14.2 + +## 3.7.1 + +Update git and casc plugins versions + +## 3.7.0 + +Added the option to create AWS SecurityGroupPolicy resources + +## 3.6.2 + +Fix httpsKeyStore mount when `controller.httpsKeyStore.enable` is `true` + +## 3.6.1 + +Update Jenkins image and appVersion to jenkins lts release version 2.303.2 + + +## 3.6.0 +Support custom agent pod labels + +## 3.5.20 +Disallow ingress on port 50000 when agent listener is disabled + +## 3.5.19 +Add support for specifying termination-log behaviour for Jenkins controller + +## 3.5.18 +Add support for creating a Pod Disruption Budget for Jenkins controller + +## 3.5.17 +Update workdingDir to `/home/jenkins/agent` + +## 3.5.16 +Update location of icon (wiki.jenkins.io is down) + +## 3.5.15 +Add support for adding labels to the Jenkins home Persistent Volume Claim (pvc) + +## 3.5.14 + +* Updated versions of default plugins +* Use verbose logging during plugin installation +* download the latest version of all plugin dependencies (Fixes #442) + +## 3.5.13 + +Update Jenkins image and appVersion to jenkins lts release version 2.303.1 + +## 3.5.12 + +Added extended documentation for Backup and Restore. + +## 3.5.11 + +Sanitized the Jenkins Label + +## 3.5.10 + +Fixed `controller.customJenkinsLabels` not getting templated into the controller `labelString:` field in JCasC + +## 3.5.9 + +Update Jenkins image and appVersion to jenkins lts release version 2.289.3 + + +## 3.5.8 + +Add parameter `backup.serviceAccount.create` to disable service account creation for backup service and `backup.serviceAccount.name` to allow change of the SA name. +`backup.annotations` was moved to `backup.serviceAccount.annotations` + +## 3.5.7 + +Enable setting `controller.serviceExternalTrafficPolicy` to set [the standard Service option](https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip). `externalTrafficPolicy` denotes if this Service desires to route external traffic to node-local or cluster-wide endpoints. + +## 3.5.6 + +Add optional `controller.initContainerResources`, if set, it will change resources allocation for init controller, overwise the `controller.resources` will be used + +## 3.5.5 + +Allow to configure nodeUsageMode via `agent.nodeUsageMode` + +## 3.5.4 + +Update tests to work with unittest 0.2.6 + +## 3.5.3 + +Update Jenkins image and appVersion to jenkins lts release version 2.289.2 + +## 3.5.2 + +Enable setting `controller.installLatestSpecifiedPlugins` to set whether to download the latest dependencies of any plugin that is requested to have the latest version. + +## 3.5.1 +Fix activeDeadlineSeconds wrong type bug in jenkins-backup-cronjob template + +## 3.5.0 + +Allow `controller.podAnnotations` to be render as a template + +## 3.4.1 + +Allow showRawYaml for the default agent's pod template to be customized. + +## 3.4.0 + +configAutoReload container updated from `kiwigrid/k8s-sidecar:0.1.275` to `kiwigrid/k8s-sidecar:1.12.2` + +## 3.3.23 + +Make `controller.ingress.resourceRootUrl` compatible with API version networking.k8s.io/v1 on k8s >= 1.19.x + +## 3.3.22 + +Update Jenkins image and appVersion to jenkins lts release version 2.289.1 + +## 3.3.21 +`persistence.mounts` additionally mount to init container to allow custom CA certificate keystore + +## 3.3.18 +Added `controller.overrideArgs` so any cli argument can be passed to the WAR. + +## 3.3.17 +Correct docs on disabling plugin installation + +## 3.3.16 +Support generating `SecretClaim` resources in order to read secrets from HashiCorp Vault into Kubernetes using `kube-vault-controller`. + +## 3.3.15 +Prevent `controller.httpsKeyStore` from improperly being quoted, leading to an invalid location on disk + +## 3.3.14 +Correct docs on disabling plugin installation + +## 3.3.13 +Update plugins + +## 3.3.12 +Add `controller.additionalExistingSecrets` property + +## 3.3.11 +Add support for disabling the Agent listener service via `controller.agentListenerEnabled`. + +## 3.3.10 +Update Jenkins image and appVersion to jenkins lts release version 2.277.4 + +## 3.3.9 +* Change helper template so user defined `agent.jenkinsUrl` value will always be used, if set +* Simplify logic for `jenkinsUrl` and `jenkinsTunnel` generation: always use fully qualified address + +## 3.3.8 +Update Jenkins image and appVersion to jenkins lts release version 2.277.3 + +## 3.3.7 +fix controller-ingress line feed bug + +## 3.3.6 + +Update Git plugin version to v4.7.1 +Update ldap plugin version to v2.5 + +## 3.3.5 + +Use tpl function for environment vars. Fixes [https://github.com/jenkinsci/helm-charts/issues/324] + +## 3.3.4 + +Update Jenkins image and appVersion to jenkins lts release version 2.277.2 + + +## 3.3.3 + +Enable setting `controller.installLatestPlugins` to set whether to download the minimum required version of all dependencies. + +## 3.3.2 + +Add `controller.additionalSecrets` documentation + +## 3.3.1 + +Add `controller.additionalSecrets` property + +## 3.3.0 + +Change default Jenkins image to `jdk11` variant + +## 3.2.6 + +Add missing `controller.jenkinsUrlProtocol` property + +## 3.2.5 + +Add additional metadata `artifacthub.io/images` for artifacthub + +## 3.2.4 +Update Jenkins image and appVersion to jenkins lts release version 2.277.1 +Update Git plugin version to v4.6.0 +Update kubernetes plugin version to v1.29.2 + +## 3.2.3 + +Fix rendering `controller.ingress.path` + +## 3.2.2 + +Added description for `controller.jenkinsUrl` value + +## 3.2.1 + +Enable setting ImagePullSecrets to controller and agent service accounts. + +## 3.2.0 + +Calculate consistent unique agent IDs to be used in pod templates. Fixes [https://github.com/jenkinsci/helm-charts/issues/270] + +## 3.1.15 + +Fix documentation for the kubernetes probes + +## 3.1.14 + +Typo in documentation + +## 3.1.13 + +Update Jenkins image and appVersion to jenkins lts release version 2.263.4 + +## 3.1.12 + +Added GitHub Action to automate the updating of LTS releases. + +## 3.1.11 + +Enable setting controller.updateStrategy to change the update strategy for StatefulSet + +## 3.1.10 + +Fixed issue for the AgentListener where it was not possible to attribute a NodePort + +## 3.1.9 + +Upgrade kubernetes plugin to 1.29.0 and CasC plugin to 1.47 + +## 3.1.8 + +Fix init scripts config map name + +## 3.1.7 + +Fix missing newline when `httpsKeyStore` is enabled + +## 3.1.6 + +Mount controller init scripts from ConfigMap + +## 3.1.5 + +Fix `namespaceOverride` not applied when loading JCasC + +## 3.1.4 + +Update Git plugin version to v4.5.2 + +## 3.1.3 + +Update Jenkins image and appVersion to jenkins lts release version 2.263.3 + +## 3.1.2 + +Enable setting maxRequestsPerHostStr to change the max concurrent connections to Kubernetes API + +## 3.1.1 + +Update Jenkins image and appVersion to jenkins lts release version 2.263.2 + +## 3.1.0 + +* Added `.Values.controller.podSecurityContextOverride` and `.Values.backup.podSecurityContextOverride`. +* Added simple default values tests for `jenkins-backup-cronjob.yaml`. + +## 3.0.14 + +Enable to only backup job folder instead of whole jenkins + +## 3.0.13 + +Improve Documentation around JCasc and Custom Image + +## 3.0.12 + +Added GitHub Action testing on Kind 1.16, 1.17, 1.18, 1.19 & 1.20 + +## 3.0.11 + +Fixes & unit tests for Ingress resources on Kubernetes 1.19 and above + +## 3.0.10 + +Ingress resources on Kubernetes 1.19 (or above) are created with the version `networking.k8s.io/v1` + +## 3.0.9 + +Added support for backing up to Azure Blob Storage. + +## 3.0.8 + +* Typo in documentation + +## 3.0.7 + +* Add support for setting default agent workspaceVolume + +## 3.0.6 + +Use 2.263.1 image + +## 3.0.5 + +* Update appVersion to reflect new jenkins lts release version 2.263.1 + +## 3.0.4 + +* Fix documentation for additional secret mounts + +## 3.0.3 + +* Update `README.md` with explanation on how to mount additional secrets + +## 3.0.2 + +* Fix `.Values.controller.tolerations` and `.Values.controller.nodeSelector` variable names in templates\jenkins-backup-cronjob.yaml + +## 3.0.1 + +* added 'runAsNonroot' to security context + +## 3.0.0 + +* Chart uses StatefulSet instead of Deployment +* XML configuration was removed in favor of JCasC +* chart migrated to helm 3.0.0 (apiVersion v2) +* offending terms have been removed +* values have been renamed and re-ordered to make it easier to use +* already deprecated items have been removed +* componentName for the controller is now `jenkins-controller` +* componentName for the agent is now `jenkins-agent` +* container names are now + * `init` for the init container which downloads Jenkins plugins + * `jenkins` for the Jenkins controller + * `config-reload` for the sidecar container which automatically reloads JCasC +* Updated UI tests to use official `bats/bats` image instead of `dduportal/bats` + +For migration instructions from previous versions and additional information check README.md. + +## 2.19.0 + +* Use lts version 2.249.3 +* Update kubernetes, workflow-aggregator, git and configuration-as-code plugins. +* Fail apply_config.sh script if an error occurs. + +## 2.18.2 + +Fix: `master.javaOpts` issue with quoted values + +## 2.18.1 + +Recommend installing plugins in custom image + +## 2.18.0 + +Removed /tmp volume. Making /tmp a volume causes permission issues with jmap/jstack on certain Kubernetes clusters + +## 2.17.1 + +Fix location of jenkins.war file. +It is located in `/usr/share/jenkins/jenkins.war` and can be fonfigured via `master.jenkinsWar`. + +## 2.17.0 + +Add support for plugin-installation-manager-tool + +## 2.16.0 + +Added Startup probe for Jenkins pod when Kubernetes cluster is 1.16 or newer + +## 2.15.5 + +scriptApproval is taken into account when enableXmlConfig is false. + +## 2.15.4 + +Add Tilt support for easier helm chart development. + +## 2.15.3 + +Fix error on missing `ingress.paths` value + +## 2.15.2 + +Added documentation for ingress and jenkins URL + +## 2.15.1 + +Fix priorityClassName entry in values.yaml file + +## 2.15.0 + +Added support for disabling the helm.sh/chart annotation + +## 2.14.0 + +Added support for annotations in podTemplates + +## 2.13.2 + +Add nodeSelector in the backup pod +Fix tolerations in the backup pod + +## 2.13.1 + +Update list of maintainers + +## 2.13.0 + +Added Support for websockets in the default Jcasc config +Added trailing slash to JENKINS_URL env var + +## 2.12.2 + +Added unit tests for most resources in the Helm chart. + +## 2.12.1 + +Helm chart readme update + +## 2.12.0 + +Add option to configure securityContext capabilities + +## 2.11.0 + +Added configurable security context for jenkins backup CronJob and annotations to its serviceaccount. + +## 2.10.0 + +Make activeDeadlineSeconds for backup job configurable + +## 2.9.0 + +Make namespace of PrometheusRule configurable + +## 2.8.2 + +Bumped configuration-as-code plugin version from 1.41 to 1.43. +See [configuration-as-code plugin issue #1478](https://github.com/jenkinsci/configuration-as-code-plugin/issues/1478) + +## 2.8.1 + +Fix indentation of JAVA_OPTS + +## 2.8.0 + +Add support for helm unittest and include first tests + +## 2.7.2 + +Target port of container `jenkins-sc-config` taken the value from values.yaml. + +## 2.7.0 + +Add a secondary ingress template for those who want a second ingress with different labels or annotations or whatever else. + +Example: You want /github-webhook to be on a public ingress, while the main Jenkins intance to be on a private locked down ingress. + +## 2.6.5 + +Update configScripts example + +## 2.6.4 + +Add timja as a maintainer + +## 2.6.3 + +Update k8s-sidecar image to 0.1.193 + +## 2.6.2 + +Only mount empty dir secrets-dir if either `master.enableXmlConfig` or `master.secretsFilesSecret` is set +Fixes #19 + +## 2.6.1 Do not render empty JCasC templates + +## 2.6.0 First release in jenkinsci GitHub org + +Updated readme for new location + +## 2.5.2 + +Fix as per JENKINS-47112 + +## 2.5.1 + +Support Jenkins Resource Root URL + +## 2.5.0 + +Add an option to specify that Jenkins master should be initialized only once, during first install. + +## 2.4.1 + +Reorder readme parameters into sections to facilitate chart usage and maintenance + +## 2.4.0 Update default agent image + +`jenkins/jnlp-slave` is deprected and `jenkins/inbound-agent` should be used instead. +Also updated it to newest version (4.3-4). + +## 2.3.3 correct templating of master.slaveJenkinsUrl + +Fixes #22708 + +## 2.3.2 Fix wrong value for overwritePluginsFromImage + +Fixes #23003 +Fixes #22633 + +Also fixes indentation for #23114 + +## 2.3.1 + +Always mount {{ .Values.master.jenkinsRef }}/secrets/ directory. Previous it +was mounted only when `master.enableXmlConfig` was enabled. + +## 2.3.0 + +Add an option to specify pod based on labels that can connect to master if NetworkPolicy is enabled + +## 2.2.0 increase retry for config auto reload + +Configure `REQ_RETRY_CONNECT` to `10` to give Jenkins more time to start up. + + +Value can be configured via `master.sidecars.configAutoReload.reqRetryConnect` + +## 2.1.2 updated readme + +## 2.1.1 update credentials-binding plugin to 1.23 + +## 2.1.0 + +Add support to set `runAsUser` and `runAsGroup` for `agent`. + +## 2.0.1 + +Only render authorizationStrategy and securityRealm when values are set. + +## 2.0.0 Configuration as Code now default + container does not run as root anymore + +The readme contains more details for this update. +Please note that the updated values contain breaking changes. + +## 1.27.0 Update plugin versions & sidecar container + +| plugin | old version | new version | +| --------------------- | ----------- | ----------- | +| kubernetes | 1.25.3 | 1.25.7 | +| workflow-job | 2.38 | 2.39 | +| credentials-binding | 1.21 | 1.22 | +| configuration-as-code | 1.39 | 1.41 | + +configAutoReload container updated from `kiwigrid/k8s-sidecar:0.1.132` to `kiwigrid/k8s-sidecar:0.1.144` + +## 1.26.0 + +Add support to override `workingDir` for default pod template + +## 1.25.0 + +Add support for installing plugins in addition to the chart's default plugins via `master.additionalPlugins` + +## 1.24.0 + +Allow configuration of yamlMergeStrategy via `agent.yamlMergeStrategy` + +## 1.23.2 + +In the `jenkins.xml.podTemplate` helper function, allow templating of all string values under `agent.volumes` except `type` by rendering them with the `tpl` function + +## 1.23.1 + +Added auto detection for Ingress API version + +## 1.23.0 + +Allow to use an existing secret for the jenkins admin credentials + +## 1.22.0 + +Add support for UI security in the default JCasC via `master.JCasC.securityRealm` and `master.JCasC.authorizationStrategy` which deny anonymous access by default + +## 1.21.3 + +Render `agent.envVars` in kubernetes pod template JCasC + +## 1.21.2 + +Cleanup `agent.yamlTemplate` rendering in kubernetes pod template XML configuration + +## 1.21.1 + +Render `agent.nodeSelector` in the kubernetes pod template JCasC + +## 1.21.0 + +Add support for overriding Ingress paths via `master.ingress.paths` + +## 1.20.0 + +Add the following options for configuring the Kubernetes plugin. + +- master.slaveDefaultsProviderTemplate +- master.slaveJenkinsUrl +- master.slaveJenkinsTunnel +- master.slaveConnectTimeout +- master.slaveReadTimeout + +## 1.19.0 + +Add support for disabling remember me via `master.disableRememberMe` +Add support for using a different markup formatter via `master.markupFormatter` + +## 1.18.1 + +Add support for executor mode configuraton with `master.executorMode`. + +## 1.18.0 Make installation of configuration-as-code plugin explicit + +Instead of configuring the configuration-as-code plugin version via +`master.JCasC.pluginVersion` it is now installed via `master.installPlugins` + +## 1.17.2 + +Allow templating of `serviceAccount.annotations` and `serviceAccountAgent.annotations` by rendering them with the `tpl` function + +## 1.17.1 + +Add support for Persistent Volume Claim (PVC) in `agent.volumes` + +## 1.17.0 + +Render `agent.volumes` in kubernetes pod template JCasC + +## 1.16.2 + +Reverts 1.16.1 as it introduced an error #22047 + +## 1.16.1 + +Fixed a bug with master.runAsUser variable due to use wrong type for comparison. + +## 1.16.0 + +Add `master.overwritePluginsFromImage` to allow support for jenkins plugins installed in the master image to persist. + +## 1.15.0 Update plugin versions & sidecar container + +| plugin | old version | new version | +| --------------------- | ----------- | ----------- | +| kubernetes | 1.25.1 | 1.25.3 | +| workflow-job | 2.36 | 2.38 | +| git | 4.2.0 | 4.2.2 | +| configuration-as-code | 1.36 | 1.39 | + +configAutoReload container updated from `kiwigrid/k8s-sidecar:0.1.20` to `kiwigrid/k8s-sidecar:0.1.132` + +## 1.14.0 + +support auto-reload container environment variables configuration + +## 1.13.3 + +Fix wrong indent in tolerations + +## 1.13.2 + +Add support for custom ClusterIP + +## 1.13.1 + +Fix `agent.yamlTemplate` rendering in kubernetes pod template JCasC + +## 1.13.0 + +Add `master.networkPolicy.internalAgents` and `master.networkPolicy.externalAgents` stanzas to fine grained controls over where internal/external agents can connect from. Internal ones are allowed based on pod labels and (optionally) namespaces, and external ones are allowed based on IP ranges. + +## 1.12.0 Support additional agents + +Add support for easy configuration of additional agents which inherit values from `agent`. + +## 1.11.3 + +Update the kubernetes plugin from 1.24.1 to 1.25.1 and grant 'watch' permission to 'events' which is required since this plugin version. + +## 1.11.2 Configure agent.args in values.yaml + +## 1.11.1 Support for master.additionalConfig + +Fixed a bug with jenkinsHome variable in range block when master.additionalConfig is set - Helm cannot evaluate field Values in type interface {}. + +## 1.11.0 Add support for configuring custom pod templates + +Add `agent.podTemplates` option for declaring custom pod templates in the default configured kubernetes cloud. + +## 1.10.1 Only copy JCasC files if there are any + +The chart always tried to copy Configuration as Code configs even if there are none. That resulted in an error which is resolved with this. + +## 1.10.0 Remove configuration-as-code-support plugins + +In recent version of configuration-as-code-plugin this is no longer necessary. + +## 1.9.24 + +Update JCasC auto-reload docs and remove stale SSH key references from version "1.8.0 JCasC auto reload works without SSH keys" + +## 1.9.23 Support jenkinsUriPrefix when JCasC is enabled + +Fixed a bug in the configuration as code reload URL, where it wouldn't work with a jenkinsUriPrefix set. + +## 1.9.22 + +Add `master.jenkinsHome` and `master.jenkinsRef` options to use docker images derivates from Jenkins + +## 1.9.21 + +Add `master.terminationGracePeriodSeconds` option + +## 1.9.20 + +Update default plugins + +- kubernetes:1.24.1 +- workflow-job:2.36 +- workflow-aggregator:2.6 +- credentials-binding:1.21 +- git:4.2.0 +- configuration-as-code:1.36 + +## 1.9.19 + +Update docs for Helm 3 + +## 1.9.18 + +Make `jenkins-home` attachable to Azure Disks without pvc + +```yaml + volumes: + - name: jenkins-home + azureDisk: + kind: Managed + diskName: myAKSDisk + diskURI: /subscriptions//resourceGroups/MC_myAKSCluster_myAKSCluster_eastus/providers/Microsoft.Compute/disks/myAKSDisk +``` + +## 1.9.16 + +Fix PodLabel for NetworkPolicy to work if enabled + +## 1.9.14 + +Properly fix case sense in `Values.master.overwriteConfig` in `config.yaml` + +## 1.9.13 + +Fix case sense in `Values.master.overwriteConfig` in `config.yaml` + +## 1.9.12 + +Scriptapprovals are overwritten when overwriteConfig is enabled + +## 1.9.10 + +Added documentation for `persistence.storageClass`. + +## 1.9.9 +Make `master.deploymentAnnotation` configurable. + +## 1.9.8 + +Make `agent.slaveConnectTimeout` configurable: by increasing this value Jenkins will not cancel&ask k8s for a pod again, while it's on `ContainerCreating`. Useful when you have big images or autoscaling takes some time. + +## 1.9.7 Update plugin versions + +| plugin | old version | new version | +|-----------------------|-------------|-------------| +| kubernetes | 1.18.2 | 1.21.2 | +| workflow-job | 2.33 | 2.36 | +| credentials-binding | 1.19 | 1.20 | +| git | 3.11.0 | 4.0.0 | +| configuration-as-code | 1.27 | 1.32 | + +## 1.9.6 + +Enables jenkins to use keystore inorder to have native ssl support #17790 + +## 1.9.5 Enable remoting security + +`Manage Jenkins` -> `Configure Global Security` -> `Enable Agent → Master Access Control` is now enabled via configuration as code plugin + +## 1.9.4 Option to set existing secret with Google Application Default Credentials + +Google application credentials are kept in a file, which has to be mounted to a pod. You can set `gcpcredentials` in `existingSecret` as follows: + +```yaml + existingSecret: + jenkins-service-account: + gcpcredentials: application_default_credentials.json +``` + +Helm template then creates the necessary volume mounts and `GOOGLE_APPLICATION_CREDENTIALS` environmental variable. + +## 1.9.3 Fix `JAVA_OPTS` when config auto-reload is enabled + +## 1.9.2 Add support for kubernetes-credentials-provider-plugin + +[kubernetes-credentials-provider-plugin](https://jenkinsci.github.io/kubernetes-credentials-provider-plugin/) needs permissions to get/watch/list kubernetes secrets in the namespaces where Jenkins is running. + +The necessary role binding can be created using `rbac.readSecrets` when `rbac.create` is `true`. + +To quote from the plugin documentation: + +> Because granting these permissions for secrets is not something that should be done lightly it is highly advised for security reasons that you both create a unique service account to run Jenkins as, and run Jenkins in a unique namespace. + +Therefor this is disabled by default. + +## 1.9.1 Update kubernetes plugin URL + +## 1.9.0 Change default serviceType to ClusterIP + +## 1.8.2 + +Revert fix in `1.7.10` since direct connection is now disabled by default. + +## 1.8.1 + +Add `master.schedulerName` to allow setting a Kubernetes custom scheduler + +## 1.8.0 JCasC auto reload works without SSH keys + +We make use of the fact that the Jenkins Configuration as Code Plugin can be triggered via http `POST` to `JENKINS_URL/configuration-as-code/reload`and a pre-shared key. +The sidecar container responsible for reloading config changes is now `kiwigrid/k8s-sidecar:0.1.20` instead of it's fork `shadwell/k8s-sidecar`. + +References: + +- [Triggering Configuration Reload](https://github.com/jenkinsci/configuration-as-code-plugin/blob/master/docs/features/configurationReload.md) +- [kiwigrid/k8s-sidecar](https://hub.docker.com/r/kiwigrid/k8s-sidecar) + +`master.sidecars.configAutoReload.enabled` now works using `casc.reload.token` + +## 1.7.10 + +Disable direct connection in default configuration (when kubernetes plugin version >= 1.20.2). +Note: In case direct connection is going to be used `jenkins/jnlp-slave` needs to be version `3.35-5` or newer. + +## 1.7.9 + +Prevented Jenkins Setup Wizard on new installations + +## 1.7.8 + +Extend extraPorts to be opened on the Service object, not just the container. + +## 1.7.7 + +Add persistentvolumeclaim permission to the role to support new dynamic pvc workspaces. + +## 1.7.6 + +Updated `master.slaveKubernetesNamespace` to parse helm templates. +Defined an sensible empty value to the following variables, to silence invalid warnings: + +- master.extraPorts +- master.scriptApproval +- master.initScripts +- master.JCasC.configScripts +- master.sidecars.other +- agent.envVars +- agent.volumes + +## 1.7.5 + +Fixed an issue where the JCasC won't run if JCasC auto-reload is enabled [issue #17135](https://github.com/helm/charts/issues/17135) + +## 1.7.4 + +Comments out JCasC example of jenkins.systemMessage so that it can be used by end users. Previously, an attempt to set systemMessage causes Jenkins to startup, citing duplicate JCasC settings for systemMessage [issue #13333](https://github.com/helm/charts/issues/13333) + +## 1.7.2 + +Update kubernetes-plugin to version 1.18.2 which fixes frequently encountered [JENKINS-59000](https://issues.jenkins-ci.org/plugins/servlet/mobile#issue/JENKINS-59000) + +## 1.7.1 + +Update the default requirements for jenkins-agent to 512Mi which fixes frequently encountered [issue #3723](https://github.com/helm/charts/issues/3723) + +## 1.7.0 + +[Jenkins Configuration as Code Plugin](https://github.com/jenkinsci/configuration-as-code-plugin) default configuration can now be enabled via `master.JCasC.defaultConfig`. + +JCasC default configuration includes: + +- Jenkins URL +- Admin email `master.jenkinsAdminEmail` +- crumbIssuer +- disableRememberMe: false +- mode: NORMAL +- numExecutors: {{ .Values.master.numExecutors }} +- projectNamingStrategy: "standard" +- kubernetes plugin + - containerCapStr via `agent.containerCap` + - jenkinsTunnel + - jenkinsUrl + - maxRequestsPerHostStr: "32" + - name: "kubernetes" + - namespace + - serverUrl: `"https://kubernetes.default"` + - template + - containers + - alwaysPullImage: `agent.alwaysPullImage` + - args + - command + - envVars + - image: `agent.image:agent.imageTag` + - name: `.agent.sideContainerName` + - privileged: `.agent.privileged` + - resourceLimitCpu: `agent.resources.limits.cpu` + - resourceLimitMemory: `agent.resources.limits.memory` + - resourceRequestCpu: `agent.resources.requests.cpu` + - resourceRequestMemory: `agent.resources.requests.memory` + - ttyEnabled: `agent.TTYEnabled` + - workingDir: "/home/jenkins" + - idleMinutes: `agent.idleMinutes` + - instanceCap: 2147483647 + - imagePullSecrets: + - name: `.agent.imagePullSecretName` + - label + - name + - nodeUsageMode: "NORMAL" + - podRetention: `agent.podRetention` + - serviceAccount + - showRawYaml: true + - slaveConnectTimeoutStr: "100" + - yaml: `agent.yamlTemplate` + - yamlMergeStrategy: "override" +- security: + - apiToken: + - creationOfLegacyTokenEnabled: false + - tokenGenerationOnCreationEnabled: false + - usageStatisticsEnabled: true + +Example `values.yaml` which enables JCasC, it's default config and configAutoReload: + +```yaml +master: + JCasC: + enabled: true + defaultConfig: true + sidecars: + configAutoReload: + enabled: true +``` + +add master.JCasC.defaultConfig and configure location + +- JCasC configuration is stored in template `jenkins.casc.defaults` + so that it can be used in `config.yaml` and `jcasc-config.yaml` + depending on if configAutoReload is enabled or not + +- Jenkins Location (URL) is configured to provide a startin point + for the config + +## 1.6.1 + +Print error message when `master.sidecars.configAutoReload.enabled` is `true`, but the admin user can't be found to configure the SSH key. + +## 1.6.0 + +Add support for Google Cloud Storage for backup CronJob (migrating from nuvo/kube-tasks to maorfr/kube-tasks) + +## 1.5.9 + +Fixed a warning when sidecar resources are provided through a parent chart or override values + +## 1.5.8 + +Fixed an issue when master.enableXmlConfig is set to false: Always mount jenkins-secrets volume if secretsFilesSecret is set (#16512) + +## 1.5.7 + +added initial changelog (#16324) +commit: cee2ebf98 + +## 1.5.6 + +enable xml config misspelling (#16477) +commit: a125b99f9 + +## 1.5.5 + +Jenkins master label (#16469) +commit: 4802d14c9 + +## 1.5.4 + +add option enableXmlConfig (#16346) +commit: 387d97a4c + +## 1.5.3 + +extracted "jenkins.URL" into template (#16347) +commit: f2fdf5332 + +## 1.5.2 + +Fix backups when deployment has custom name (#16279) +commit: 16b89bfff + +## 1.5.1 + +Ability to set custom namespace for ServiceMonitor (#16145) +commit: 18ee6cf01 + +## 1.5.0 + +update Jenkins plugins to fix security issue (#16069) +commit: 603cf2d2b + +## 1.4.3 + +Use fixed container name (#16068) +commit: b3e4b4a49 + +## 1.4.2 + +Provide default job value (#15963) +commit: c462e2017 + +## 1.4.1 + +Add Jenkins backendconfig values (#15471) +commit: 7cc9b54c7 + +## 1.4.0 + +Change the value name for docker image tags - standartise to helm preferred value name - tag; this also allows auto-deployments using weaveworks flux (#15565) +commit: 5c3d920e7 + +## 1.3.6 + +jenkins deployment port should be target port (#15503) +commit: 83909ebe3 + +## 1.3.5 + +Add support for namespace specification (#15202) +commit: e773201a6 + +## 1.3.4 + +Adding sub-path option for scraping (#14833) +commit: e04021154 + +## 1.3.3 + +Add existingSecret to Jenkins backup AWS credentials (#13392) +commit: d9374f57d + +## 1.3.2 + +Fix JCasC version (#14992) +commit: 26a6d2b99 + +## 1.3.1 + +Update affinity for a backup cronjob (#14886) +commit: c21ed8331 + +## 1.3.0 + +only install casc support plugin when needed (#14862) +commit: a56fc0540 + +## 1.2.2 + +DNS Zone customization (#14775) +commit: da2910073 + +## 1.2.1 + +only render comment if configAutoReload is enabled (#14754) +commit: e07ead283 + +## 1.2.0 + +update plugins to latest version (#14744) +commit: 84336558e + +## 1.1.24 + +add example for EmptyDir volume (#14499) +commit: cafb60209 + +## 1.1.23 + +check if installPlugins is set before using it (#14168) +commit: 1218f0359 + +## 1.1.22 + +Support servicemonitor and alerting rules (#14124) +commit: e15a27f48 + +## 1.1.21 + +Fix: healthProbe timeouts mapping to initial delay (#13875) +commit: 825b32ece + +## 1.1.20 + +Properly handle overwrite config for additional configs (#13915) +commit: 18ce9b558 + +## 1.1.18 + +update maintainer (#13897) +commit: 223002b27 + +## 1.1.17 + +add apiVersion (#13795) +commit: cd1e5c35a + +## 1.1.16 + +allow changing of the target port to support TLS termination sidecar (#13576) +commit: a34d3bbcc + +## 1.1.15 + +fix wrong pod selector in jenkins-backup (#13542) +commit: b5df4fd7e + +## 1.1.14 + +allow templating of customInitContainers (#13536) +commit: d1e1421f4 + +## 1.1.13 + +fix #13467 (wrong deprecation message) (#13511) +commit: fbe28fa1c + +## 1.1.12 + +Correct customInitContainers Name example. (#13405) +commit: 6c6e40405 + +## 1.1.11 + +fix master.runAsUser, master.fsGroup examples (#13389) +commit: 2d7e5bf72 + +## 1.1.10 + +Ability to specify raw yaml template (#13319) +commit: 77aaa9a5f + +## 1.1.9 + +correct NOTES.txt - use master.ingress.hostname (#13318) +commit: b08ef6280 + +## 1.1.8 + +explain how to upgrade major versions (#13273) +commit: e7617a97e + +## 1.1.7 + +Add support for idleMinutes and serviceAccount (#13263) +commit: 4595ee033 + +## 1.1.6 + +Use same JENKINS_URL no matter if slaves use different namespace (#12564) +commit: 94c90339f + +## 1.1.5 + +fix deprecation checks (#13224) +commit: c7d2f8105 + +## 1.1.4 + +Fix issue introduced in #13136 (#13232) +commit: 0dbcded2e + +## 1.1.3 + +fix chart errors (#13197) +commit: 692a1e3da + +## 1.1.2 + +correct selector for jenkins pod (#13200) +commit: 4537e7fda + +## 1.1.1 + +Fix rendering of customInitContainers and lifecycle for Jenkins helm chart (#13189) +commit: e8f6b0ada + +## 1.1.0 + +Add support for openshift route in jenkins (#12973) +commit: 48c58a430 + +## 1.0.0 + +helm chart best practices (#13136) +commit: b02ae3f48 + +### Breaking changes + +- values have been renamed to follow helm chart best practices for naming conventions so + that all variables start with a lowercase letter and words are separated with camelcase + +- all resources are now using recommended standard labels + + +As a result of the label changes also the selectors of the deployment have been updated. +Those are immutable so trying an updated will cause an error like: + +```text +Error: Deployment.apps "jenkins" is invalid: spec.selector: Invalid value: v1.LabelSelector{MatchLabels:map[string]string{"app.kubernetes.io/component":"jenkins-master", "app.kubernetes.io/instance":"jenkins"}, MatchExpressions:[]v1.LabelSelectorRequirement(nil)}: field is immutable +``` + +In order to upgrade, delete the Jenkins Deployment before upgrading: + +```console +kubectl delete deploy jenkins +``` + +## 0.40.0 + +Allow to override jenkins location protocol (#12257) +commit: 18a830626 + +## 0.39.0 + +Add possibility to add custom init-container and lifecycle for master-container (#13062) +commit: 14d043593 + +## 0.38.0 + +Support `priorityClassName` on Master Deployment (#13069) +commit: e896c62bc + +## 0.37.3 + +Add support for service account annotations in jenkins (#12969) +commit: b22774e2f + +## 0.37.2 + +fix: add hostName to ingress in values.yaml (#12946) +commit: 041045e9b + +## 0.37.1 + +Update to match actual defaults in value.yaml (#12904) +commit: 73b6d37eb + +## 0.37.0 + +Support multiple Jenkins instances in same namespace (#12748) +commit: 32ff2f343 + +## 0.36.5 + +Fix wrong comment in values.yaml (#12761) +commit: 9db8ced23 + +## 0.36.4 + +Re-add value for Ingress API Version (#12753) +commit: ecb7791b5 + +## 0.36.3 + +allow templating of volumes (#12734) +commit: adbda2ca6 + +## 0.36.2 + +Fix self-introduced whitespace bug (#12528) +commit: eec1678eb + +## 0.36.1 + +Add flag to overwrite jobs definition from values.yaml (#12427) +commit: fd349b2fc + +## 0.36.0 + +Replace OwnSshKey with AdminSshKey (#12140) (#12466) +commit: 80a8c9eb6 + +## 0.35.2 + +add note for breaking changes (#12203) +commit: e779c5a54 + +## 0.35.1 + +Allow Jenkins to run with READONLYROOTFS psp (#12338) +commit: 7c419e191 + +## 0.35.0 + +Jenkins OverwriteConfig setting also overwrites init scripts (#9468) +commit: 501335b76 + +## 0.34.1 + +Fix typo on hostname variable (#12156) +commit: 3d337d8dd + +## 0.34.0 + +Allow ingress without host rule (#11960) +commit: ddc966d1e + +## 0.33.2 + +Improve documentation - clarify that rbac is needed for autoreload (#11739) +commit: 9d75a5c34 + +## 0.33.1 + +use object for rollingUpdate (#11909) +commit: cb9cf21e8 + +## 0.33.0 + +Add hostAliases (#11701) +commit: 0b89e1094 + +## 0.32.10 + +Fix slave jnlp port always being reset when container is restarted (#11685) +commit: d7d51797b + +## 0.32.9 + +add ingress Hostname an ApiVersion to docs (#11576) +commit: 4d3e77137 + +## 0.32.8 + +Support custom master pod labels in deployment (#9714) (#11511) +commit: 9de96faa0 + +## 0.32.7 + +Fix Markdown syntax in readme (#11496) +commit: a32221a95 + +## 0.32.6 + +Added custom labels on jenkins ingress (#11466) +commit: c875d2b9b + +## 0.32.5 + +fix typo in default jenkins agent image fixes #11356 (#11463) +commit: 30adb9a91 + +## 0.32.4 + +fix incorrect Deployment when using sidecars (#11413) +commit: 362b4cef8 + +## 0.32.3 + +[]: #10131 (#11411) +commit: 49cb72055 + +## 0.32.2 + +Option to expose the slave listener port as host port (#11187) +commit: 2f85a9663 + +## 0.32.1 + +Updating Jenkins deployment fails appears rollingUpdate needs to be (#11166) +commit: 07fc9dbde + +## 0.32.0 + +Merge Sidecard configs (#11339) +commit: 3696090b9 + +## 0.31.0 + +Add option to overwrite plugins (#11231) +commit: 0e9aa00a5 + +## 0.30.0 + +Added slave Pod env vars (#8743) +commit: 1499f6608 + +## 0.29.3 + +revert indentation to previous working version (#11293) +commit: 61662f17a + +## 0.29.2 + +allow running sidecar containers for Jenkins master (#10950) +commit: 9084ce54a + +## 0.29.1 + +Indent lines related to EnableRawHtmlMarkupFormatter (#11252) +commit: 20b310c08 + +## 0.29.0 + +Jenkins Configuration as Code (#9057) +commit: c3e8c0b17 + +## 0.28.11 + +Allow to enable OWASP Markup Formatter Plugin (#10851) +commit: 9486e5ddf + +## 0.28.10 + +Fixes #1341 -- update Jenkins chart documentation (#10290) +commit: 411c81cd0 + +## 0.28.9 + +Quoted JavaOpts values (#10671) +commit: 926a843a8 + +## 0.28.8 + +Support custom labels in deployment (#9714) (#10533) +commit: 3e00b47fa + +## 0.28.7 + +separate test resources (#10597) +commit: 7b7ae2d11 + +## 0.28.6 + +allow customizing livenessProbe periodSeconds (#10534) +commit: 3c94d250d + +## 0.28.5 + +Add role kind option (#8498) +commit: e791ad124 + +## 0.28.4 + +workaround for busybox's cp (Closes: #10471) (#10497) +commit: 0d51a4187 + +## 0.28.3 + +fix parsing java options (#10140) +commit: 9448d0293 + +## 0.28.2 + +Fix job definitions in standard values.yaml (#10184) +commit: 6b6355ae7 + +## 0.28.1 + +add numExecutors as a variable in values file (#10236) +commit: d5ea2050f + +## 0.28.0 + +various (#10223) +commit: e17d2a65d + +## 0.27.0 + +add backup cronjob (#10095) +commit: 863ead8db + +## 0.26.2 + +add namespace flag for port-forwarding in jenkins notes (#10399) +commit: 846b589a9 + +## 0.26.1 + +- fixes #10267 when executed with helm template - otherwise produces an invalid template. (#10403) + commit: 266f9d839 + +## 0.26.0 + +Add subPath for jenkins-home mount (#9671) +commit: a9c76ac9b + +## 0.25.1 + +update readme to indicate the correct image that is used by default (#9915) +commit: 6aba9631c + +## 0.25.0 + +Add ability to manually set Jenkins URL (#7405) +commit: a0178fcb4 + +## 0.24.0 + +Make AuthorizationStrategy configurable (#9567) +commit: 06545b226 + +## 0.23.0 + +Update Jenkins public chart (#9296) +commit: 4e5f5918b + +## 0.22.0 + +allow to override jobs (#9004) +commit: dca9f9ab9 + +## 0.21.0 + +Simple implementation of the option to define the ingress path to the jenkins service (#8101) +commit: 013159609 + +## 0.20.2 + +Cosmetic change to remove necessity of changing "appVersion" for every new LTS release (#8866) +commit: f52af042a + +## 0.20.1 + +Added ExtraPorts to open in the master pod (#7759) +commit: 78858a2fb + +## 0.19.1 + +Fix component label in NOTES.txt ... (#8300) +commit: c5494dbfe + +## 0.19.0 + +Kubernetes 1.9 support as well as automatic apiVersion detection (#7988) +commit: 6853ad364 + +## 0.18.1 + +Respect SlaveListenerPort value in config.xml (#7220) +commit: 0a5ddac35 + +## 0.18.0 + +Allow replacement of Jenkins config with configMap. (#7450) +commit: c766da3de + +## 0.17.0 + +Add option to allow host networking (#7530) +commit: dc2eeff32 + +## 0.16.25 + +add custom jenkins labels to the build agent (#7167) +commit: 3ecde5dbf + +## 0.16.24 + +Move kubernetes and job plugins to latest versions (#7438) +commit: 019e39456 + +## 0.16.23 + +Add different Deployment Strategies based on persistence (#6132) +commit: e0a20b0b9 + +## 0.16.22 + +avoid linting errors when adding Values.Ingress.Annotations (#7425) +commit: 99eacc854 + +## 0.16.21 + +bump appVersion to reflect new jenkins lts release version 2.121.3 (#7217) +commit: 296df165d + +## 0.16.20 + +Configure kubernetes plugin for including namespace value (#7164) +commit: c0dc6cc48 + +## 0.16.19 + +make pod retention policy setting configurable (#6962) +commit: e614c1033 + +## 0.16.18 + +Update plugins version (#6988) +commit: bf8180018 + +## 0.16.17 + +Add Master.AdminPassword in readme (#6987) +commit: 13e754ad7 + +## 0.16.16 + +Added jenkins location configuration (#6573) +commit: 79de7026c + +## 0.16.15 + +use generic env var, not oracle specific env var (#6116) +commit: 6084ab4a4 + +## 0.16.14 + +Allow to specify resource requests and limits on initContainers (#6723) +commit: 942a33b1a + +## 0.16.13 + +Added support for NodePort service type for jenkens agent svc (#6571) +commit: 89a213c2b + +## 0.16.12 + +Added ability to configure multiple LoadBalancerSourceRanges (#6243) +commit: 01604ddbc + +## 0.16.11 + +Removing ContainerPort configuration as at the moment it does not work when you change this setting (#6411) +commit: e1c0468bd + +## 0.16.9 + +Fix jobs parsing for configmap by adding toYaml to jobs.yaml template (#3747) +commit: b2542a123 + +## 0.16.8 + +add jenkinsuriprefix in healthprobes (#5737) +commit: 435d7a7b9 + +## 0.16.7 + +Added the ability to switch from ClusterRoleBinding to RoleBinding. (#6190) +commit: dde03ede0 + +## 0.16.6 + +Make jenkins master pod security context optional (#6122) +commit: 63653fd59 + +## 0.16.5 + +Rework resources requests and limits (#6077) (#6077) +commit: e738f99d0 + +## 0.16.4 + +Add jenkins master pod annotations (#6313) +commit: 5e7325721 + +## 0.16.3 + +Split Jenkins readiness and liveness probe periods (#5704) +commit: fc6100c38 + +## 0.16.1 + +fix typo in jenkins readme (#5228) +commit: 3cd3f4b8b + +## 0.16.0 + +Inherit existing plugins from Jenkins image (#5409) +commit: fd93bff82 + +## 0.15.1 + +Allow NetworkPolicy.ApiVersion and Master.Ingress.ApiVersion to Differ (#5103) +commit: 78ee4ba15 + +## 0.15.0 + +Secure Defaults (#5026) +commit: 0fe90b520 + +## 0.14.6 + +Wait for up to 2 minutes before failing liveness check (#5161) +commit: 2cd3fc481 + +## 0.14.5 + +correct ImageTag setting (#4371) +commit: 8ea04174d + +## 0.14.4 + +Update jenkins/README.md (#4559) +commit: d4e6352dd + +## 0.14.3 + +Bump appVersion (#4177) +commit: 605d3d441 + +## 0.14.2 + +Master.InitContainerEnv: Init Container Env Vars (#3495) +commit: c64abe27d + +## 0.14.1 + +Allow more configuration of Jenkins agent service (#4028) +commit: fc82f39b2 + +## 0.14.0 + +Add affinity settings (#3839) +commit: 64e82fa6a + +## 0.13.5 + +bump test timeouts (#3886) +commit: cd05dd99c + +## 0.13.4 + +Add OWNERS to jenkins chart (#3881) +commit: 1c106b9c8 + +## 0.13.3 + +Add fullnameOverride support (#3705) +commit: ec8080839 + +## 0.13.2 + +Update README.md (#3638) +commit: f6d274c37 + +## 0.13.1 + +Lower initial healthcheck delay (#3463) +commit: 9b99db67c + +## 0.13.0 + +Provision credentials.xml, secrets files and jobs (#3316) +commit: d305c5961 + +## 0.12.1 + +fix the default value for nodeUsageMode. (#3299) +commit: b68d19516 + +## 0.12.0 + +Recreate pods when CustomConfigMap is true and there are changes to the ConfigMap (which is how the vanilla chart works) (#3181) +commit: 86d29f804 + +## 0.11.1 + +Optionally adds liveness and readiness probes to jenkins (#3245) +commit: 8b9aa73ee + +## 0.11.0 + +Feature/run jenkins as non root user (#2899) +commit: 8918f4175 + +## 0.10.3 + +template the version to keep them synced (#3084) +commit: 35e7fa49a + +## 0.10.2 + +Update Chart.yaml +commit: e3e617a0b + +## 0.10.1 + +Merge branch 'master' into jenkins-test-timeout +commit: 9a230a6b1 + +Double retry count for Jenkins test +commit: 129c8e824 + +Jenkins: Update readme | Master.ServiceAnnotations (#2757) +commit: 6571810bc + +## 0.10.0 + +Update Jenkins images and plugins (#2496) +commit: 2e2622682 + +## 0.9.4 + +Updating to remove the `.lock` directory as well (#2747) +commit: 6e676808f + +## 0.9.3 + +Use variable for service port when testing (#2666) +commit: d044f99be + +## 0.9.2 + +Review jenkins networkpolicy docs (#2618) +commit: 49911e458 + +Add image pull secrets to jenkins templates (#1389) +commit: 4dfae21fd + +## 0.9.1 + +Added persistent volume claim annotations (#2619) +commit: ac9e5306e + +Fix failing CI lint (#2758) +commit: 26f709f0e + +## 0.9.0 + +namespace defined templates with chart name (#2140) +commit: 408ae0b3f + +## 0.8.9 + +added useSecurity and adminUser to params (#1903) +commit: 39d2a03cd + +Use storageClassName for jenkins. (#1997) +commit: 802f6449b + +## 0.8.8 + +Remove old plugin locks before installing plugins (#1746) +commit: 6cd7b8ff4 + +promote initContainrs to podspec (#1740) +commit: fecc804fc + +## 0.8.7 + +add optional LoadBalancerIP option. (#1568) +commit: d39f11408 + +## 0.8.6 + +Fix bad key in values.yaml (#1633) +commit: dc27e5af3 + +## 0.8.5 + +Update Jenkins to support node selectors for agents. (#1532) +commit: 4af5810ff + +## 0.8.4 + +Add support for supplying JENKINS_OPTS and/or URI prefix (#1405) +commit: 6a331901a + +## 0.8.3 + +Add serviceAccountName to deployment (#1477) +commit: 0dc349b44 + +## 0.8.2 + +Remove path from ingress specification to allow other paths (#1599) +commit: e727f6b32 + +Update git plugin to 3.4.0 for CVE-2017-1000084 (#1505) +commit: 03482f995 + +## 0.8.1 + +Use consistent whitespace in template placeholders (#1437) +commit: 912f50c71 + +add configurable service annotations #1234 (#1244) +commit: 286861ca8 + +## 0.8.0 + +Jenkins v0.8.0 (#1385) +commit: 0009a2393 + +## 0.7.4 + +Use imageTag as version in config map (#1333) +commit: e8bb6ebb4 + +## 0.7.3 + +Add NetworkPolicy to Jenkins (#1228) +commit: 572b36c6d + +## 0.7.2 + +- Workflow plugin pin (#1178) + commit: ac3a0c7bc + +## 0.7.1 + +copy over plugins.txt in case of update (#1222) +commit: 75b5b1174 + +## 0.7.0 + +add jmx option (#964) +commit: 6ae8d1945 + +## 0.6.4 + +update jenkins to latest LTS 2.46.3 (#1182) +commit: ad90b4c27 + +## 0.6.3 + +Update chart maints to gh u/n (#1107) +commit: f357b77ed + +## 0.6.2 + +Add Agent.Privileged option (#957) +commit: 2cf4aced2 + +## 0.6.1 + +Upgrade jenkins to 2.46.2 (#971) +commit: 41bd742b4 + +## 0.6.0 + +Smoke test for Jenkins Chart (#944) +commit: 110441054 + +## 0.5.1 + +removed extra space from hardcoded password (#925) +commit: 85a9b9123 + +## 0.5.0 + +move config to init-container allowing use of upstream containers (#921) +commit: 1803c3d33 + +## 0.4.1 + +add ability to toggle jnlp-agent podTemplate generation (#918) +commit: accd53203 + +## 0.4.0 + +Jenkins add script approval (#916) +commit: c1746656e + +## 0.3.1 + +Update Jenkins to Latest LTS fixes #731 (#733) +commit: e9a3aed8b + +## 0.3.0 + +Added option to add Jenkins init scripts (#617) +commit: b889623d0 + +## 0.2.0 + +Add existing PVC (#716) +commit: 05271f145 + +## 0.1.15 + +use Master.ServicePort in config.xml (#769) +commit: f351f4b16 + +## 0.1.14 + +Added option to disable security on master node (#403) +commit: 3a6113d18 + +## 0.1.13 + +Added: extra mount points support for jenkins master (#474) +commit: fab0f7eb1 + +## 0.1.12 + +fix storageclass config typo (#548) +commit: 6fc0ff242 + +## 0.1.10 + +Changed default value of Kubernetes Cloud name to match one in kubernetes plugin (#404) +commit: 68351304a + +Add support for overriding the Jenkins ConfigMap (#524) +commit: f97ca53b1 + +## 0.1.9 + +Added jenkins-master ingress support (#402) +commit: d76a09588 + +## 0.1.8 + +Change description (#553) +commit: 91f5c24e1 + +Removed default Persistence.StorageClass: generic (#530) +commit: c87494c10 + +Update to the recommended pvc patterns. (#448) +commit: a7fc595aa + +Remove helm.sh/created annotations (#505) +commit: f380da2fb + +## 0.1.7 + +add support for explicit NodePort on jenkins chart (#342) +commit: f63c188da + +Add configurable loadBalancerSourceRanges for jenkins chart (#360) +commit: 44007c50e + +Update Jenkins version to current LTS (2.19.4) and Kubernetes Plugin to 0.10 (#341) +commit: 6c8678167 + +## 0.1.6 + +Add imagePullPolicy to init container (#295) +commit: 103ee1952 + +## 0.1.5 + +bump chart version with PVC metadata label additions +commit: 4aa9cf5b1 + +## 0.1.4 + +removed `*` from `jenkins/templates/NOTES.txt` +commit: 76212230b + +apply standard metadata labels to PVC's +commit: 58b730836 + +specify namespace in `kubectl get svc` commands in NOTES.txt +commit: 7d3287e81 + +Update Jenkins version to current LTS (#194) +commit: 2c0404049 + +## 0.1.1 + +escape fixed +commit: 2026e1d15 + +.status.loadBalancer.ingress[0].ip is empty in AWS +commit: 1810e37f4 + +.status.loadBalancer.ingress[0].ip is empty in AWS +commit: 3cbd3ced6 + +Remove 'Getting Started:' from various NOTES.txt. (#181) +commit: 2f63fd524 + +docs(\*): update readmes to reference chart repos (#119) +commit: c7d1bff05 + +## 0.1.0 + +Move first batch of PVC charts to stable +commit: d745f4879 diff --git a/charts/jenkins/jenkins/5.5.0/Chart.yaml b/charts/jenkins/jenkins/5.5.0/Chart.yaml new file mode 100644 index 000000000..cb6b016ea --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/Chart.yaml @@ -0,0 +1,54 @@ +annotations: + artifacthub.io/category: integration-delivery + artifacthub.io/changes: | + - Introduce capability of set skipTlsVerify and usageRestricted flags in additionalClouds + artifacthub.io/images: | + - name: jenkins + image: docker.io/jenkins/jenkins:2.452.3-jdk17 + - name: k8s-sidecar + image: docker.io/kiwigrid/k8s-sidecar:1.27.5 + - name: inbound-agent + image: jenkins/inbound-agent:3256.v88a_f6e922152-1 + artifacthub.io/license: Apache-2.0 + artifacthub.io/links: | + - name: Chart Source + url: https://github.com/jenkinsci/helm-charts/tree/main/charts/jenkins + - name: Jenkins + url: https://www.jenkins.io/ + - name: support + url: https://github.com/jenkinsci/helm-charts/issues + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: Jenkins + catalog.cattle.io/kube-version: '>=1.14-0' + catalog.cattle.io/release-name: jenkins +apiVersion: v2 +appVersion: 2.452.3 +description: 'Jenkins - Build great things at any scale! As the leading open source + automation server, Jenkins provides over 1800 plugins to support building, deploying + and automating any project. ' +home: https://www.jenkins.io/ +icon: file://assets/icons/jenkins.svg +keywords: +- jenkins +- ci +- devops +kubeVersion: '>=1.14-0' +maintainers: +- email: maor.friedman@redhat.com + name: maorfr +- email: mail@torstenwalter.de + name: torstenwalter +- email: garridomota@gmail.com + name: mogaal +- email: wmcdona89@gmail.com + name: wmcdona89 +- email: timjacomb1@gmail.com + name: timja +name: jenkins +sources: +- https://github.com/jenkinsci/jenkins +- https://github.com/jenkinsci/docker-inbound-agent +- https://github.com/maorfr/kube-tasks +- https://github.com/jenkinsci/configuration-as-code-plugin +type: application +version: 5.5.0 diff --git a/charts/jenkins/jenkins/5.5.0/README.md b/charts/jenkins/jenkins/5.5.0/README.md new file mode 100644 index 000000000..4ddd1faa4 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/README.md @@ -0,0 +1,706 @@ +# Jenkins + +[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/jenkins)](https://artifacthub.io/packages/helm/jenkinsci/jenkins) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![Releases downloads](https://img.shields.io/github/downloads/jenkinsci/helm-charts/total.svg)](https://github.com/jenkinsci/helm-charts/releases) +[![Join the chat at https://app.gitter.im/#/room/#jenkins-ci:matrix.org](https://badges.gitter.im/badge.svg)](https://app.gitter.im/#/room/#jenkins-ci:matrix.org) + +[Jenkins](https://www.jenkins.io/) is the leading open source automation server, Jenkins provides over 1800 plugins to support building, deploying and automating any project. + +This chart installs a Jenkins server which spawns agents on [Kubernetes](http://kubernetes.io) utilizing the [Jenkins Kubernetes plugin](https://plugins.jenkins.io/kubernetes/). + +Inspired by the awesome work of [Carlos Sanchez](https://github.com/carlossg). + +## Get Repository Info + +```console +helm repo add jenkins https://charts.jenkins.io +helm repo update +``` + +_See [`helm repo`](https://helm.sh/docs/helm/helm_repo/) for command documentation._ + +## Install Chart + +```console +# Helm 3 +$ helm install [RELEASE_NAME] jenkins/jenkins [flags] +``` + +_See [configuration](#configuration) below._ + +_See [helm install](https://helm.sh/docs/helm/helm_install/) for command documentation._ + +## Uninstall Chart + +```console +# Helm 3 +$ helm uninstall [RELEASE_NAME] +``` + +This removes all the Kubernetes components associated with the chart and deletes the release. + +_See [helm uninstall](https://helm.sh/docs/helm/helm_uninstall/) for command documentation._ + +## Upgrade Chart + +```console +# Helm 3 +$ helm upgrade [RELEASE_NAME] jenkins/jenkins [flags] +``` + +_See [helm upgrade](https://helm.sh/docs/helm/helm_upgrade/) for command documentation._ + +Visit the chart's [CHANGELOG](https://github.com/jenkinsci/helm-charts/blob/main/charts/jenkins/CHANGELOG.md) to view the chart's release history. +For migration between major version check [migration guide](#migration-guide). + +## Building weekly releases + +The default charts target Long-Term-Support (LTS) releases of Jenkins. +To use other versions the easiest way is to update the image tag to the version you want. +You can also rebuild the chart if you want the `appVersion` field to match. + +## Configuration + +See [Customizing the Chart Before Installing](https://helm.sh/docs/intro/using_helm/#customizing-the-chart-before-installing). +To see all configurable options with detailed comments, visit the chart's [values.yaml](https://github.com/jenkinsci/helm-charts/blob/main/charts/jenkins/values.yaml), or run these configuration commands: + +```console +# Helm 3 +$ helm show values jenkins/jenkins +``` + +For a summary of all configurable options, see [VALUES_SUMMARY.md](https://github.com/jenkinsci/helm-charts/blob/main/charts/jenkins/VALUES_SUMMARY.md). + +### Configure Security Realm and Authorization Strategy + +This chart configured a `securityRealm` and `authorizationStrategy` as shown below: + +```yaml +controller: + JCasC: + securityRealm: |- + local: + allowsSignup: false + enableCaptcha: false + users: + - id: "${chart-admin-username}" + name: "Jenkins Admin" + password: "${chart-admin-password}" + authorizationStrategy: |- + loggedInUsersCanDoAnything: + allowAnonymousRead: false +``` + +With the configuration above there is only a single user. +This is fine for getting started quickly, but it needs to be adjusted for any serious environment. + +So you should adjust this to suite your needs. +That could be using LDAP / OIDC / .. as authorization strategy and use globalMatrix as authorization strategy to configure more fine-grained permissions. + +### Consider using a custom image + +This chart allows the user to specify plugins which should be installed. However, for production use cases one should consider to build a custom Jenkins image which has all required plugins pre-installed. +This way you can be sure which plugins Jenkins is using when starting up and you avoid trouble in case of connectivity issues to the Jenkins update site. + +The [docker repository](https://github.com/jenkinsci/docker) for the Jenkins image contains [documentation](https://github.com/jenkinsci/docker#preinstalling-plugins) how to do it. + +Here is an example how that can be done: + +```Dockerfile +FROM jenkins/jenkins:lts +RUN jenkins-plugin-cli --plugins kubernetes workflow-aggregator git configuration-as-code +``` + +NOTE: If you want a reproducible build then you should specify a non-floating tag for the image `jenkins/jenkins:2.249.3` and specify plugin versions. + +Once you built the image and pushed it to your registry you can specify it in your values file like this: + +```yaml +controller: + image: "registry/my-jenkins" + tag: "v1.2.3" + installPlugins: false +``` + +Notice: `installPlugins` is set to false to disable plugin download. In this case, the image `registry/my-jenkins:v1.2.3` must have the plugins specified as default value for [the `controller.installPlugins` directive](https://github.com/jenkinsci/helm-charts/blob/main/charts/jenkins/VALUES_SUMMARY.md#jenkins-plugins) to ensure that the configuration side-car system works as expected. + +In case you are using a private registry you can use 'imagePullSecretName' to specify the name of the secret to use when pulling the image: + +```yaml +controller: + image: "registry/my-jenkins" + tag: "v1.2.3" + imagePullSecretName: registry-secret + installPlugins: false +``` + +### External URL Configuration + +If you are using the ingress definitions provided by this chart via the `controller.ingress` block the configured hostname will be the ingress hostname starting with `https://` or `http://` depending on the `tls` configuration. +The Protocol can be overwritten by specifying `controller.jenkinsUrlProtocol`. + +If you are not using the provided ingress you can specify `controller.jenkinsUrl` to change the URL definition. + +### Configuration as Code + +Jenkins Configuration as Code (JCasC) is now a standard component in the Jenkins project. +To allow JCasC's configuration from the helm values, the plugin [`configuration-as-code`](https://plugins.jenkins.io/configuration-as-code/) must be installed in the Jenkins Controller's Docker image (which is the case by default as specified by the [default value of the directive `controller.installPlugins`](https://github.com/jenkinsci/helm-charts/blob/main/charts/jenkins/VALUES_SUMMARY.md#jenkins-plugins)). + +JCasc configuration is passed through Helm values under the key `controller.JCasC`. +The section ["Jenkins Configuration as Code (JCasC)" of the page "VALUES_SUMMARY.md"](https://github.com/jenkinsci/helm-charts/blob/main/charts/jenkins/VALUES_SUMMARY.md#jenkins-configuration-as-code-jcasc) lists all the possible directives. + +In particular, you may specify custom JCasC scripts by adding sub-key under the `controller.JCasC.configScripts` for each configuration area where each corresponds to a plugin or section of the UI. + +The sub-keys (prior to `|` character) are only labels used to give the section a meaningful name. +The only restriction is they must conform to RFC 1123 definition of a DNS label, so they may only contain lowercase letters, numbers, and hyphens. + +Each key will become the name of a configuration yaml file on the controller in `/var/jenkins_home/casc_configs` (by default) and will be processed by the Configuration as Code Plugin during Jenkins startup. + +The lines after each `|` become the content of the configuration yaml file. + +The first line after this is a JCasC root element, e.g. jenkins, credentials, etc. + +Best reference is the Documentation link here: `https:///configuration-as-code`. + +The example below sets custom systemMessage: + +```yaml +controller: + JCasC: + configScripts: + welcome-message: | + jenkins: + systemMessage: Welcome to our CI\CD server. +``` + +More complex example that creates ldap settings: + +```yaml +controller: + JCasC: + configScripts: + ldap-settings: | + jenkins: + securityRealm: + ldap: + configurations: + - server: ldap.acme.com + rootDN: dc=acme,dc=uk + managerPasswordSecret: ${LDAP_PASSWORD} + groupMembershipStrategy: + fromUserRecord: + attributeName: "memberOf" +``` + +Keep in mind that default configuration file already contains some values that you won't be able to override under configScripts section. + +For example, you can not configure Jenkins URL and System Admin email address like this because of conflicting configuration error. + +Incorrect: + +```yaml +controller: + JCasC: + configScripts: + jenkins-url: | + unclassified: + location: + url: https://example.com/jenkins + adminAddress: example@mail.com +``` + +Correct: + +```yaml +controller: + jenkinsUrl: https://example.com/jenkins + jenkinsAdminEmail: example@mail.com +``` + +Further JCasC examples can be found [here](https://github.com/jenkinsci/configuration-as-code-plugin/tree/master/demos). + +#### Breaking out large Config as Code scripts + +Jenkins Config as Code scripts can become quite large, and maintaining all of your scripts within one yaml file can be difficult. The Config as Code plugin itself suggests updating the `CASC_JENKINS_CONFIG` environment variable to be a comma separated list of paths for the plugin to traverse, picking up the yaml files as needed. +However, under the Jenkins helm chart, this `CASC_JENKINS_CONFIG` value is maintained through the templates. A better solution is to split your `controller.JCasC.configScripts` into separate values files, and provide each file during the helm install. + +For example, you can have a values file (e.g values_main.yaml) that defines the values described in the `VALUES_SUMMARY.md` for your Jenkins configuration: + +```yaml +jenkins: + controller: + jenkinsUrlProtocol: https + installPlugins: false + ... +``` + +In a second file (e.g values_jenkins_casc.yaml), you can define a section of your config scripts: + +```yaml +jenkins: + controller: + JCasC: + configScripts: + jenkinsCasc: | + jenkins: + disableRememberMe: false + mode: NORMAL + ... +``` + +And keep extending your config scripts by creating more files (so not all config scripts are located in one yaml file for better maintenance): + +values_jenkins_unclassified.yaml + +```yaml +jenkins: + controller: + JCasC: + configScripts: + unclassifiedCasc: | + unclassified: + ... +``` + +When installing, you provide all relevant yaml files (e.g `helm install -f values_main.yaml -f values_jenkins_casc.yaml -f values_jenkins_unclassified.yaml ...`). Instead of updating the `CASC_JENKINS_CONFIG` environment variable to include multiple paths, multiple CasC yaml files will be created in the same path `var/jenkins_home/casc_configs`. + +#### Config as Code With or Without Auto-Reload + +Config as Code changes (to `controller.JCasC.configScripts`) can either force a new pod to be created and only be applied at next startup, or can be auto-reloaded on-the-fly. +If you set `controller.sidecars.configAutoReload.enabled` to `true`, a second, auxiliary container will be installed into the Jenkins controller pod, known as a "sidecar". +This watches for changes to configScripts, copies the content onto the Jenkins file-system and issues a POST to `http:///reload-configuration-as-code` with a pre-shared key. +You can monitor this sidecar's logs using command `kubectl logs -c config-reload -f`. +If you want to enable auto-reload then you also need to configure rbac as the container which triggers the reload needs to watch the config maps: + +```yaml +controller: + sidecars: + configAutoReload: + enabled: true +rbac: + create: true +``` + +### Allow Limited HTML Markup in User-Submitted Text + +Some third-party systems (e.g. GitHub) use HTML-formatted data in their payload sent to a Jenkins webhook (e.g. URL of a pull-request being built). +To display such data as processed HTML instead of raw text set `controller.enableRawHtmlMarkupFormatter` to true. +This option requires installation of the [OWASP Markup Formatter Plugin (antisamy-markup-formatter)](https://plugins.jenkins.io/antisamy-markup-formatter/). +This plugin is **not** installed by default but may be added to `controller.additionalPlugins`. + +### Change max connections to Kubernetes API +When using agents with containers other than JNLP, The kubernetes plugin will communicate with those containers using the Kubernetes API. this changes the maximum concurrent connections +```yaml +agent: + maxRequestsPerHostStr: "32" +``` +This will change the configuration of the kubernetes "cloud" (as called by jenkins) that is created automatically as part of this helm chart. + +### Change container cleanup timeout API +For tasks that use very large images, this timeout can be increased to avoid early termination of the task while the Kubernetes pod is still deploying. +```yaml +agent: + retentionTimeout: "32" +``` +This will change the configuration of the kubernetes "cloud" (as called by jenkins) that is created automatically as part of this helm chart. + +### Change seconds to wait for pod to be running +This will change how long Jenkins will wait (seconds) for pod to be in running state. +```yaml +agent: + waitForPodSec: "32" +``` +This will change the configuration of the kubernetes "cloud" (as called by jenkins) that is created automatically as part of this helm chart. + +### Mounting Volumes into Agent Pods + +Your Jenkins Agents will run as pods, and it's possible to inject volumes where needed: + +```yaml +agent: + volumes: + - type: Secret + secretName: jenkins-mysecrets + mountPath: /var/run/secrets/jenkins-mysecrets +``` + +The supported volume types are: `ConfigMap`, `EmptyDir`, `HostPath`, `Nfs`, `PVC`, `Secret`. +Each type supports a different set of configurable attributes, defined by [the corresponding Java class](https://github.com/jenkinsci/kubernetes-plugin/tree/master/src/main/java/org/csanchez/jenkins/plugins/kubernetes/volumes). + +### NetworkPolicy + +To make use of the NetworkPolicy resources created by default, install [a networking plugin that implements the Kubernetes NetworkPolicy spec](https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy#before-you-begin). + +[Install](#install-chart) helm chart with network policy enabled by setting `networkPolicy.enabled` to `true`. + +You can use `controller.networkPolicy.internalAgents` and `controller.networkPolicy.externalAgents` stanzas for fine-grained controls over where internal/external agents can connect from. +Internal ones are allowed based on pod labels and (optionally) namespaces, and external ones are allowed based on IP ranges. + +### Script approval list + +`controller.scriptApproval` allows to pass function signatures that will be allowed in pipelines. +Example: + +```yaml +controller: + scriptApproval: + - "method java.util.Base64$Decoder decode java.lang.String" + - "new java.lang.String byte[]" + - "staticMethod java.util.Base64 getDecoder" +``` + +### Custom Labels + +`controller.serviceLabels` can be used to add custom labels in `jenkins-controller-svc.yaml`. +For example: + +```yaml +ServiceLabels: + expose: true +``` + +### Persistence + +The Jenkins image stores persistence under `/var/jenkins_home` path of the container. +A dynamically managed Persistent Volume Claim is used to keep the data across deployments, by default. +This is known to work in GCE, AWS, and minikube. Alternatively, a previously configured Persistent Volume Claim can be used. + +It is possible to mount several volumes using `persistence.volumes` and `persistence.mounts` parameters. +See additional `persistence` values using [configuration commands](#configuration). + +#### Existing PersistentVolumeClaim + +1. Create the PersistentVolume +2. Create the PersistentVolumeClaim +3. [Install](#install-chart) the chart, setting `persistence.existingClaim` to `PVC_NAME` + +#### Long Volume Attach/Mount Times + +Certain volume type and filesystem format combinations may experience long +attach/mount times, [10 or more minutes][K8S_VOLUME_TIMEOUT], when using +`fsGroup`. This issue may result in the following entries in the pod's event +history: + +```console +Warning FailedMount 38m kubelet, aks-default-41587790-2 Unable to attach or mount volumes: unmounted volumes=[jenkins-home], unattached volumes=[plugins plugin-dir jenkins-token-rmq2g sc-config-volume tmp jenkins-home jenkins-config secrets-dir]: timed out waiting for the condition +``` + +In these cases, experiment with replacing `fsGroup` with +`supplementalGroups` in the pod's `securityContext`. This can be achieved by +setting the `controller.podSecurityContextOverride` Helm chart value to +something like: + +```yaml +controller: + podSecurityContextOverride: + runAsNonRoot: true + runAsUser: 1000 + supplementalGroups: [1000] +``` + +This issue has been reported on [azureDisk with ext4][K8S_VOLUME_TIMEOUT] and +on [Alibaba cloud][K8S_VOLUME_TIMEOUT_ALIBABA]. + +[K8S_VOLUME_TIMEOUT]: https://github.com/kubernetes/kubernetes/issues/67014 +[K8S_VOLUME_TIMEOUT_ALIBABA]: https://github.com/kubernetes/kubernetes/issues/67014#issuecomment-698770511 + +#### Storage Class + +It is possible to define which storage class to use, by setting `persistence.storageClass` to `[customStorageClass]`. +If set to a dash (`-`), dynamic provisioning is disabled. +If the storage class is set to null or left undefined (`""`), the default provisioner is used (gp2 on AWS, standard on GKE, AWS & OpenStack). + +### Additional Secrets + +Additional secrets and Additional Existing Secrets, +can be mounted into the Jenkins controller through the chart or created using `controller.additionalSecrets` or `controller.additionalExistingSecrets`. +A common use case might be identity provider credentials if using an external LDAP or OIDC-based identity provider. +The secret may then be referenced in JCasC configuration (see [JCasC configuration](#configuration-as-code)). + +`values.yaml` controller section, referencing mounted secrets: +```yaml +controller: + # the 'name' and 'keyName' are concatenated with a '-' in between, so for example: + # an existing secret "secret-credentials" and a key inside it named "github-password" should be used in Jcasc as ${secret-credentials-github-password} + # 'name' and 'keyName' must be lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', + # and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc') + # existingSecret existing secret "secret-credentials" and a key inside it named "github-username" should be used in Jcasc as ${github-username} + # When using existingSecret no need to specify the keyName under additionalExistingSecrets. + existingSecret: secret-credentials + + additionalExistingSecrets: + - name: secret-credentials + keyName: github-username + - name: secret-credentials + keyName: github-password + - name: secret-credentials + keyName: token + + additionalSecrets: + - name: client_id + value: abc123 + - name: client_secret + value: xyz999 + JCasC: + securityRealm: | + oic: + clientId: ${client_id} + clientSecret: ${client_secret} + ... + configScripts: + jenkins-casc-configs: | + credentials: + system: + domainCredentials: + - credentials: + - string: + description: "github access token" + id: "github_app_token" + scope: GLOBAL + secret: ${secret-credentials-token} + - usernamePassword: + description: "github access username password" + id: "github_username_pass" + password: ${secret-credentials-github-password} + scope: GLOBAL + username: ${secret-credentials-github-username} +``` + +For more information, see [JCasC documentation](https://github.com/jenkinsci/configuration-as-code-plugin/blob/master/docs/features/secrets.adoc#kubernetes-secrets). + +### Secret Claims from HashiCorp Vault + +It's possible for this chart to generate `SecretClaim` resources in order to automatically create and maintain Kubernetes `Secrets` from HashiCorp [Vault](https://www.vaultproject.io/) via [`kube-vault-controller`](https://github.com/roboll/kube-vault-controller) + +These `Secrets` can then be referenced in the same manner as Additional Secrets above. + +This can be achieved by defining required Secret Claims within `controller.secretClaims`, as follows: +```yaml +controller: + secretClaims: + - name: jenkins-secret + path: secret/path + - name: jenkins-short-ttl + path: secret/short-ttl-path + renew: 60 +``` + +### RBAC + +RBAC is enabled by default. If you want to disable it you will need to set `rbac.create` to `false`. + +### Adding Custom Pod Templates + +It is possible to add custom pod templates for the default configured kubernetes cloud. +Add a key under `agent.podTemplates` for each pod template. Each key (prior to `|` character) is just a label, and can be any value. +Keys are only used to give the pod template a meaningful name. The only restriction is they may only contain RFC 1123 \ DNS label characters: lowercase letters, numbers, and hyphens. Each pod template can contain multiple containers. +There's no need to add the _jnlp_ container since the kubernetes plugin will automatically inject it into the pod. +For this pod templates configuration to be loaded the following values must be set: + +```yaml +controller.JCasC.defaultConfig: true +``` + +The example below creates a python pod template in the kubernetes cloud: + +```yaml +agent: + podTemplates: + python: | + - name: python + label: jenkins-python + serviceAccount: jenkins + containers: + - name: python + image: python:3 + command: "/bin/sh -c" + args: "cat" + ttyEnabled: true + privileged: true + resourceRequestCpu: "400m" + resourceRequestMemory: "512Mi" + resourceLimitCpu: "1" + resourceLimitMemory: "1024Mi" +``` + +Best reference is `https:///configuration-as-code/reference#Cloud-kubernetes`. + +### Adding Pod Templates Using additionalAgents + +`additionalAgents` may be used to configure additional kubernetes pod templates. +Each additional agent corresponds to `agent` in terms of the configurable values and inherits all values from `agent` so you only need to specify values which differ. +For example: + +```yaml +agent: + podName: default + customJenkinsLabels: default + # set resources for additional agents to inherit + resources: + limits: + cpu: "1" + memory: "2048Mi" + +additionalAgents: + maven: + podName: maven + customJenkinsLabels: maven + # An example of overriding the jnlp container + # sideContainerName: jnlp + image: jenkins/jnlp-agent-maven + tag: latest + python: + podName: python + customJenkinsLabels: python + sideContainerName: python + image: python + tag: "3" + command: "/bin/sh -c" + args: "cat" + TTYEnabled: true +``` + +### Ingress Configuration + +This chart provides ingress resources configurable via the `controller.ingress` block. + +The simplest configuration looks like the following: + +```yaml +controller: + ingress: + enabled: true + paths: [] + apiVersion: "extensions/v1beta1" + hostName: jenkins.example.com +``` + +This snippet configures an ingress rule for exposing jenkins at `jenkins.example.com` + +You can define labels and annotations via `controller.ingress.labels` and `controller.ingress.annotations` respectively. +Additionally, you can configure the ingress tls via `controller.ingress.tls`. +By default, this ingress rule exposes all paths. +If needed this can be overwritten by specifying the wanted paths in `controller.ingress.paths` + +If you want to configure a secondary ingress e.g. you don't want the jenkins instance exposed but still want to receive webhooks you can configure `controller.secondaryingress`. +The secondaryingress doesn't expose anything by default and has to be configured via `controller.secondaryingress.paths`: + +```yaml +controller: + ingress: + enabled: true + apiVersion: "extensions/v1beta1" + hostName: "jenkins.internal.example.com" + annotations: + kubernetes.io/ingress.class: "internal" + secondaryingress: + enabled: true + apiVersion: "extensions/v1beta1" + hostName: "jenkins-scm.example.com" + annotations: + kubernetes.io/ingress.class: "public" + paths: + - /github-webhook +``` + +## Prometheus Metrics + +If you want to expose Prometheus metrics you need to install the [Jenkins Prometheus Metrics Plugin](https://github.com/jenkinsci/prometheus-plugin). +It will expose an endpoint (default `/prometheus`) with metrics where a Prometheus Server can scrape. + +If you have implemented [Prometheus Operator](https://github.com/prometheus-operator/prometheus-operator), you can set `controller.prometheus.enabled` to `true` to configure a `ServiceMonitor` and `PrometheusRule`. +If you want to further adjust alerting rules you can do so by configuring `controller.prometheus.alertingrules` + +If you have implemented Prometheus without using the operator, you can leave `controller.prometheus.enabled` set to `false`. + +### Running Behind a Forward Proxy + +The controller pod uses an Init Container to install plugins etc. If you are behind a corporate proxy it may be useful to set `controller.initContainerEnv` to add environment variables such as `http_proxy`, so that these can be downloaded. + +Additionally, you may want to add env vars for the init container, the Jenkins container, and the JVM (`controller.javaOpts`): + +```yaml +controller: + initContainerEnv: + - name: http_proxy + value: "http://192.168.64.1:3128" + - name: https_proxy + value: "http://192.168.64.1:3128" + - name: no_proxy + value: "" + - name: JAVA_OPTS + value: "-Dhttps.proxyHost=proxy_host_name_without_protocol -Dhttps.proxyPort=3128" + containerEnv: + - name: http_proxy + value: "http://192.168.64.1:3128" + - name: https_proxy + value: "http://192.168.64.1:3128" + javaOpts: >- + -Dhttp.proxyHost=192.168.64.1 + -Dhttp.proxyPort=3128 + -Dhttps.proxyHost=192.168.64.1 + -Dhttps.proxyPort=3128 +``` + +### HTTPS Keystore Configuration + +[This configuration](https://wiki.jenkins.io/pages/viewpage.action?pageId=135468777) enables jenkins to use keystore in order to serve HTTPS. +Here is the [value file section](https://wiki.jenkins.io/pages/viewpage.action?pageId=135468777#RunningJenkinswithnativeSSL/HTTPS-ConfigureJenkinstouseHTTPSandtheJKSkeystore) related to keystore configuration. +Keystore itself should be placed in front of `jenkinsKeyStoreBase64Encoded` key and in base64 encoded format. To achieve that after having `keystore.jks` file simply do this: `cat keystore.jks | base64` and paste the output in front of `jenkinsKeyStoreBase64Encoded`. +After enabling `httpsKeyStore.enable` make sure that `httpPort` and `targetPort` are not the same, as `targetPort` will serve HTTPS. +Do not set `controller.httpsKeyStore.httpPort` to `-1` because it will cause readiness and liveliness prob to fail. +If you already have a kubernetes secret that has keystore and its password you can specify its' name in front of `jenkinsHttpsJksSecretName`, You need to remember that your secret should have proper data key names `jenkins-jks-file` (or override the key name using `jenkinsHttpsJksSecretKey`) +and `https-jks-password` (or override the key name using `jenkinsHttpsJksPasswordSecretKey`; additionally you can make it get the password from a different secret using `jenkinsHttpsJksPasswordSecretName`). Example: + +```yaml +controller: + httpsKeyStore: + enable: true + jenkinsHttpsJksSecretName: '' + httpPort: 8081 + path: "/var/jenkins_keystore" + fileName: "keystore.jks" + password: "changeit" + jenkinsKeyStoreBase64Encoded: '' +``` +### AWS Security Group Policies + +To create SecurityGroupPolicies set `awsSecurityGroupPolicies.enabled` to true and add your policies. Each policy requires a `name`, array of `securityGroupIds` and a `podSelector`. Example: + +```yaml +awsSecurityGroupPolicies: + enabled: true + policies: + - name: "jenkins-controller" + securityGroupIds: + - sg-123456789 + podSelector: + matchExpressions: + - key: app.kubernetes.io/component + operator: In + values: + - jenkins-controller +``` + +### Agent Direct Connection + +Set `directConnection` to `true` to allow agents to connect directly to a given TCP port without having to negotiate a HTTP(S) connection. This can allow you to have agent connections without an external HTTP(S) port. Example: + +```yaml +agent: + jenkinsTunnel: "jenkinsci-agent:50000" + directConnection: true +``` + +## Migration Guide + +### From stable repository + +Upgrade an existing release from `stable/jenkins` to `jenkins/jenkins` seamlessly by ensuring you have the latest [repository info](#get-repository-info) and running the [upgrade commands](#upgrade-chart) specifying the `jenkins/jenkins` chart. + +### Major Version Upgrades + +Chart release versions follow [SemVer](../../CONTRIBUTING.md#versioning), where a MAJOR version change (example `1.0.0` -> `2.0.0`) indicates an incompatible breaking change needing manual actions. + +See [UPGRADING.md](./UPGRADING.md) for a list of breaking changes diff --git a/charts/jenkins/jenkins/5.5.0/UPGRADING.md b/charts/jenkins/jenkins/5.5.0/UPGRADING.md new file mode 100644 index 000000000..0ff90112d --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/UPGRADING.md @@ -0,0 +1,148 @@ +# Upgrade Notes + +## To 5.0.0 +- `controller.image`, `controller.tag`, and `controller.tagLabel` have been removed. If you want to overwrite the image you now need to configure any or all of: + - `controller.image.registry` + - `controller.image.repository` + - `controller.image.tag` + - `controller.image.tagLabel` +- `controller.imagePullPolicy` has been removed. If you want to overwrite the pull policy you now need to configure `controller.image.pullPolicy`. +- `controller.sidecars.configAutoReload.image` has been removed. If you want to overwrite the configAutoReload image you now need to configure any or all of: + - `controller.sidecars.configAutoReload.image.registry` + - `controller.sidecars.configAutoReload.image.repository` + - `controller.sidecars.configAutoReload.image.tag` +- `controller.sidecars.other` has been renamed to `controller.sidecars.additionalSidecarContainers`. +- `agent.image` and `agent.tag` have been removed. If you want to overwrite the agent image you now need to configure any or all of: + - `agent.image.repository` + - `agent.image.tag` + - The registry can still be overwritten by `agent.jnlpregistry` +- `agent.additionalContainers[*].image` has been renamed to `agent.additionalContainers[*].image.repository` +- `agent.additionalContainers[*].tag` has been renamed to `agent.additionalContainers[*].image.tag` +- `additionalAgents.*.image` has been renamed to `additionalAgents.*.image.repository` +- `additionalAgents.*.tag` has been renamed to `additionalAgents.*.image.tag` +- `additionalClouds.*.additionalAgents.*.image` has been renamed to `additionalClouds.*.additionalAgents.*.image.repository` +- `additionalClouds.*.additionalAgents.*.tag` has been renamed to `additionalClouds.*.additionalAgents.*.image.tag` +- `helmtest.bats.image` has been split up to: + - `helmtest.bats.image.registry` + - `helmtest.bats.image.repository` + - `helmtest.bats.image.tag` +- `controller.adminUsername` and `controller.adminPassword` have been renamed to `controller.admin.username` and `controller.admin.password` respectively +- `controller.adminSecret` has been renamed to `controller.admin.createSecret` +- `backup.*` was unmaintained and has thus been removed. See the following page for alternatives: [Kubernetes Backup and Migrations](https://nubenetes.com/kubernetes-backup-migrations/). + +## To 4.0.0 +Removes automatic `remotingSecurity` setting when using a container tag older than `2.326` (introduced in [`3.11.7`](./CHANGELOG.md#3117)). If you're using a version older than `2.326`, you should explicitly set `.controller.legacyRemotingSecurityEnabled` to `true`. + +## To 3.0.0 + +* Check `securityRealm` and `authorizationStrategy` and adjust it. + Otherwise, your configured users and permissions will be overridden. +* You need to use helm version 3 as the `Chart.yaml` uses `apiVersion: v2`. +* All XML configuration options have been removed. + In case those are still in use you need to migrate to configuration as code. + Upgrade guide to 2.0.0 contains pointers how to do that. +* Jenkins is now using a `StatefulSet` instead of a `Deployment` +* terminology has been adjusted that's also reflected in values.yaml + The following values from `values.yaml` have been renamed: + + * `master` => `controller` + * `master.useSecurity` => `controller.adminSecret` + * `master.slaveListenerPort` => `controller.agentListenerPort` + * `master.slaveHostPort` => `controller.agentListenerHostPort` + * `master.slaveKubernetesNamespace` => `agent.namespace` + * `master.slaveDefaultsProviderTemplate` => `agent.defaultsProviderTemplate` + * `master.slaveJenkinsUrl` => `agent.jenkinsUrl` + * `master.slaveJenkinsTunnel` => `agent.jenkinsTunnel` + * `master.slaveConnectTimeout` => `agent.kubernetesConnectTimeout` + * `master.slaveReadTimeout` => `agent.kubernetesReadTimeout` + * `master.slaveListenerServiceAnnotations` => `controller.agentListenerServiceAnnotations` + * `master.slaveListenerServiceType` => `controller.agentListenerServiceType` + * `master.slaveListenerLoadBalancerIP` => `controller.agentListenerLoadBalancerIP` + * `agent.slaveConnectTimeout` => `agent.connectTimeout` +* Removed values: + + * `master.imageTag`: use `controller.image` and `controller.tag` instead + * `slave.imageTag`: use `agent.image` and `agent.tag` instead + +## To 2.0.0 + +Configuration as Code is now default + container does not run as root anymore. + +### Configuration as Code new default + +Configuration is done via [Jenkins Configuration as Code Plugin](https://github.com/jenkinsci/configuration-as-code-plugin) by default. +That means that changes in values which result in a configuration change are always applied. +In contrast, the XML configuration was only applied during the first start and never altered. + +:exclamation::exclamation::exclamation: +Attention: +This also means if you manually altered configuration then this will most likely be reset to what was configured by default. +It also applies to `securityRealm` and `authorizationStrategy` as they are also configured using configuration as code. +:exclamation::exclamation::exclamation: + +### Image does not run as root anymore + +It's not recommended to run containers in Kubernetes as `root`. + +❗Attention: If you had not configured a different user before then you need to ensure that your image supports the user and group ID configured and also manually change permissions of all files so that Jenkins is still able to use them. + +### Summary of updated values + +As version 2.0.0 only updates default values and nothing else it's still possible to migrate to this version and opt out of some or all new defaults. +All you have to do is ensure the old values are set in your installation. + +Here we show which values have changed and the previous default values: + +```yaml +controller: + runAsUser: 1000 # was unset before + fsGroup: 1000 # was unset before + JCasC: + enabled: true # was false + defaultConfig: true # was false + sidecars: + configAutoReload: + enabled: true # was false +``` + +### Migration steps + +Migration instructions heavily depend on your current setup. +So think of the list below more as a general guideline of what should be done. + +- Ensure that the Jenkins image you are using contains a user with ID 1000 and a group with the same ID. + That's the case for `jenkins/jenkins:lts` image, which the chart uses by default +- Make a backup of your existing installation especially the persistent volume +- Ensure that you have the configuration as code plugin installed +- Export your current settings via the plugin: + `Manage Jenkins` -> `Configuration as Code` -> `Download Configuration` +- prepare your values file for the update e.g. add additional configuration as code setting that you need. + The export taken from above might be a good starting point for this. + In addition, the [demos](https://github.com/jenkinsci/configuration-as-code-plugin/tree/master/demos) from the plugin itself are quite useful. +- Test drive those setting on a separate installation +- Put Jenkins to Quiet Down mode so that it does not accept new jobs + `/quietDown` +- Change permissions of all files and folders to the new user and group ID: + + ```console + kubectl exec -it -c jenkins /bin/bash + chown -R 1000:1000 /var/jenkins_home + ``` + +- Update Jenkins + +## To 1.0.0 + +Breaking changes: + +- Values have been renamed to follow [helm recommended naming conventions](https://helm.sh/docs/chart_best_practices/#naming-conventions) so that all variables start with a lowercase letter and words are separated with camelcase +- All resources are now using [helm recommended standard labels](https://helm.sh/docs/chart_best_practices/#standard-labels) + +As a result of the label changes also the selectors of the deployment have been updated. +Those are immutable so trying an updated will cause an error like: + +```console +Error: Deployment.apps "jenkins" is invalid: spec.selector: Invalid value: v1.LabelSelector{MatchLabels:map[string]string{"app.kubernetes.io/component":"jenkins-controller", "app.kubernetes.io/instance":"jenkins"}, MatchExpressions:[]v1.LabelSelectorRequirement(nil)}: field is immutable +``` + +In order to upgrade, [uninstall](./README.md#uninstall-chart) the Jenkins Deployment before upgrading: diff --git a/charts/jenkins/jenkins/5.5.0/VALUES.md b/charts/jenkins/jenkins/5.5.0/VALUES.md new file mode 100644 index 000000000..23f5eda8a --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/VALUES.md @@ -0,0 +1,311 @@ +# Jenkins + +## Configuration + +The following tables list the configurable parameters of the Jenkins chart and their default values. + +## Values + +| Key | Type | Description | Default | +|:----|:-----|:---------|:------------| +| [additionalAgents](./values.yaml#L1169) | object | Configure additional | `{}` | +| [additionalClouds](./values.yaml#L1194) | object | | `{}` | +| [agent.TTYEnabled](./values.yaml#L1087) | bool | Allocate pseudo tty to the side container | `false` | +| [agent.additionalContainers](./values.yaml#L1122) | list | Add additional containers to the agents | `[]` | +| [agent.alwaysPullImage](./values.yaml#L980) | bool | Always pull agent container image before build | `false` | +| [agent.annotations](./values.yaml#L1118) | object | Annotations to apply to the pod | `{}` | +| [agent.args](./values.yaml#L1081) | string | Arguments passed to command to execute | `"${computer.jnlpmac} ${computer.name}"` | +| [agent.command](./values.yaml#L1079) | string | Command to execute when side container starts | `nil` | +| [agent.componentName](./values.yaml#L948) | string | | `"jenkins-agent"` | +| [agent.connectTimeout](./values.yaml#L1116) | int | Timeout in seconds for an agent to be online | `100` | +| [agent.containerCap](./values.yaml#L1089) | int | Max number of agents to launch | `10` | +| [agent.customJenkinsLabels](./values.yaml#L945) | list | Append Jenkins labels to the agent | `[]` | +| [agent.defaultsProviderTemplate](./values.yaml#L907) | string | The name of the pod template to use for providing default values | `""` | +| [agent.directConnection](./values.yaml#L951) | bool | | `false` | +| [agent.disableDefaultAgent](./values.yaml#L1140) | bool | Disable the default Jenkins Agent configuration | `false` | +| [agent.enabled](./values.yaml#L905) | bool | Enable Kubernetes plugin jnlp-agent podTemplate | `true` | +| [agent.envVars](./values.yaml#L1062) | list | Environment variables for the agent Pod | `[]` | +| [agent.hostNetworking](./values.yaml#L959) | bool | Enables the agent to use the host network | `false` | +| [agent.idleMinutes](./values.yaml#L1094) | int | Allows the Pod to remain active for reuse until the configured number of minutes has passed since the last step was executed on it | `0` | +| [agent.image.repository](./values.yaml#L938) | string | Repository to pull the agent jnlp image from | `"jenkins/inbound-agent"` | +| [agent.image.tag](./values.yaml#L940) | string | Tag of the image to pull | `"3256.v88a_f6e922152-1"` | +| [agent.imagePullSecretName](./values.yaml#L947) | string | Name of the secret to be used to pull the image | `nil` | +| [agent.inheritYamlMergeStrategy](./values.yaml#L1114) | bool | Controls whether the defined yaml merge strategy will be inherited if another defined pod template is configured to inherit from the current one | `false` | +| [agent.jenkinsTunnel](./values.yaml#L915) | string | Overrides the Kubernetes Jenkins tunnel | `nil` | +| [agent.jenkinsUrl](./values.yaml#L911) | string | Overrides the Kubernetes Jenkins URL | `nil` | +| [agent.jnlpregistry](./values.yaml#L935) | string | Custom registry used to pull the agent jnlp image from | `nil` | +| [agent.kubernetesConnectTimeout](./values.yaml#L921) | int | The connection timeout in seconds for connections to Kubernetes API. The minimum value is 5 | `5` | +| [agent.kubernetesReadTimeout](./values.yaml#L923) | int | The read timeout in seconds for connections to Kubernetes API. The minimum value is 15 | `15` | +| [agent.livenessProbe](./values.yaml#L970) | object | | `{}` | +| [agent.maxRequestsPerHostStr](./values.yaml#L925) | string | The maximum concurrent connections to Kubernetes API | `"32"` | +| [agent.namespace](./values.yaml#L931) | string | Namespace in which the Kubernetes agents should be launched | `nil` | +| [agent.nodeSelector](./values.yaml#L1073) | object | Node labels for pod assignment | `{}` | +| [agent.nodeUsageMode](./values.yaml#L943) | string | | `"NORMAL"` | +| [agent.podLabels](./values.yaml#L933) | object | Custom Pod labels (an object with `label-key: label-value` pairs) | `{}` | +| [agent.podName](./values.yaml#L1091) | string | Agent Pod base name | `"default"` | +| [agent.podRetention](./values.yaml#L989) | string | | `"Never"` | +| [agent.podTemplates](./values.yaml#L1150) | object | Configures extra pod templates for the default kubernetes cloud | `{}` | +| [agent.privileged](./values.yaml#L953) | bool | Agent privileged container | `false` | +| [agent.resources](./values.yaml#L961) | object | Resources allocation (Requests and Limits) | `{"limits":{"cpu":"512m","memory":"512Mi"},"requests":{"cpu":"512m","memory":"512Mi"}}` | +| [agent.restrictedPssSecurityContext](./values.yaml#L986) | bool | Set a restricted securityContext on jnlp containers | `false` | +| [agent.retentionTimeout](./values.yaml#L927) | int | Time in minutes after which the Kubernetes cloud plugin will clean up an idle worker that has not already terminated | `5` | +| [agent.runAsGroup](./values.yaml#L957) | string | Configure container group | `nil` | +| [agent.runAsUser](./values.yaml#L955) | string | Configure container user | `nil` | +| [agent.secretEnvVars](./values.yaml#L1066) | list | Mount a secret as environment variable | `[]` | +| [agent.showRawYaml](./values.yaml#L993) | bool | | `true` | +| [agent.sideContainerName](./values.yaml#L1083) | string | Side container name | `"jnlp"` | +| [agent.skipTlsVerify](./values.yaml#L917) | bool | Disables the verification of the controller certificate on remote connection. This flag correspond to the "Disable https certificate check" flag in kubernetes plugin UI | `false` | +| [agent.usageRestricted](./values.yaml#L919) | bool | Enable the possibility to restrict the usage of this agent to specific folder. This flag correspond to the "Restrict pipeline support to authorized folders" flag in kubernetes plugin UI | `false` | +| [agent.volumes](./values.yaml#L1000) | list | Additional volumes | `[]` | +| [agent.waitForPodSec](./values.yaml#L929) | int | Seconds to wait for pod to be running | `600` | +| [agent.websocket](./values.yaml#L950) | bool | Enables agent communication via websockets | `false` | +| [agent.workingDir](./values.yaml#L942) | string | Configure working directory for default agent | `"/home/jenkins/agent"` | +| [agent.workspaceVolume](./values.yaml#L1035) | object | Workspace volume (defaults to EmptyDir) | `{}` | +| [agent.yamlMergeStrategy](./values.yaml#L1112) | string | Defines how the raw yaml field gets merged with yaml definitions from inherited pod templates. Possible values: "merge" or "override" | `"override"` | +| [agent.yamlTemplate](./values.yaml#L1101) | string | The raw yaml of a Pod API Object to merge into the agent spec | `""` | +| [awsSecurityGroupPolicies.enabled](./values.yaml#L1320) | bool | | `false` | +| [awsSecurityGroupPolicies.policies[0].name](./values.yaml#L1322) | string | | `""` | +| [awsSecurityGroupPolicies.policies[0].podSelector](./values.yaml#L1324) | object | | `{}` | +| [awsSecurityGroupPolicies.policies[0].securityGroupIds](./values.yaml#L1323) | list | | `[]` | +| [checkDeprecation](./values.yaml#L1317) | bool | Checks if any deprecated values are used | `true` | +| [clusterZone](./values.yaml#L21) | string | Override the cluster name for FQDN resolving | `"cluster.local"` | +| [controller.JCasC.authorizationStrategy](./values.yaml#L533) | string | Jenkins Config as Code Authorization Strategy-section | `"loggedInUsersCanDoAnything:\n allowAnonymousRead: false"` | +| [controller.JCasC.configMapAnnotations](./values.yaml#L538) | object | Annotations for the JCasC ConfigMap | `{}` | +| [controller.JCasC.configScripts](./values.yaml#L507) | object | List of Jenkins Config as Code scripts | `{}` | +| [controller.JCasC.configUrls](./values.yaml#L504) | list | Remote URLs for configuration files. | `[]` | +| [controller.JCasC.defaultConfig](./values.yaml#L498) | bool | Enables default Jenkins configuration via configuration as code plugin | `true` | +| [controller.JCasC.overwriteConfiguration](./values.yaml#L502) | bool | Whether Jenkins Config as Code should overwrite any existing configuration | `false` | +| [controller.JCasC.security](./values.yaml#L514) | object | Jenkins Config as Code security-section | `{"apiToken":{"creationOfLegacyTokenEnabled":false,"tokenGenerationOnCreationEnabled":false,"usageStatisticsEnabled":true}}` | +| [controller.JCasC.securityRealm](./values.yaml#L522) | string | Jenkins Config as Code Security Realm-section | `"local:\n allowsSignup: false\n enableCaptcha: false\n users:\n - id: \"${chart-admin-username}\"\n name: \"Jenkins Admin\"\n password: \"${chart-admin-password}\""` | +| [controller.additionalExistingSecrets](./values.yaml#L459) | list | List of additional existing secrets to mount | `[]` | +| [controller.additionalPlugins](./values.yaml#L409) | list | List of plugins to install in addition to those listed in controller.installPlugins | `[]` | +| [controller.additionalSecrets](./values.yaml#L468) | list | List of additional secrets to create and mount | `[]` | +| [controller.admin.createSecret](./values.yaml#L91) | bool | Create secret for admin user | `true` | +| [controller.admin.existingSecret](./values.yaml#L94) | string | The name of an existing secret containing the admin credentials | `""` | +| [controller.admin.password](./values.yaml#L81) | string | Admin password created as a secret if `controller.admin.createSecret` is true | `` | +| [controller.admin.passwordKey](./values.yaml#L86) | string | The key in the existing admin secret containing the password | `"jenkins-admin-password"` | +| [controller.admin.userKey](./values.yaml#L84) | string | The key in the existing admin secret containing the username | `"jenkins-admin-user"` | +| [controller.admin.username](./values.yaml#L78) | string | Admin username created as a secret if `controller.admin.createSecret` is true | `"admin"` | +| [controller.affinity](./values.yaml#L660) | object | Affinity settings | `{}` | +| [controller.agentListenerEnabled](./values.yaml#L318) | bool | Create Agent listener service | `true` | +| [controller.agentListenerExternalTrafficPolicy](./values.yaml#L328) | string | Traffic Policy of for the agentListener service | `nil` | +| [controller.agentListenerHostPort](./values.yaml#L322) | string | Host port to listen for agents | `nil` | +| [controller.agentListenerLoadBalancerIP](./values.yaml#L358) | string | Static IP for the agentListener LoadBalancer | `nil` | +| [controller.agentListenerLoadBalancerSourceRanges](./values.yaml#L330) | list | Allowed inbound IP for the agentListener service | `["0.0.0.0/0"]` | +| [controller.agentListenerNodePort](./values.yaml#L324) | string | Node port to listen for agents | `nil` | +| [controller.agentListenerPort](./values.yaml#L320) | int | Listening port for agents | `50000` | +| [controller.agentListenerServiceAnnotations](./values.yaml#L353) | object | Annotations for the agentListener service | `{}` | +| [controller.agentListenerServiceType](./values.yaml#L350) | string | Defines how to expose the agentListener service | `"ClusterIP"` | +| [controller.backendconfig.annotations](./values.yaml#L763) | object | backendconfig annotations | `{}` | +| [controller.backendconfig.apiVersion](./values.yaml#L757) | string | backendconfig API version | `"extensions/v1beta1"` | +| [controller.backendconfig.enabled](./values.yaml#L755) | bool | Enables backendconfig | `false` | +| [controller.backendconfig.labels](./values.yaml#L761) | object | backendconfig labels | `{}` | +| [controller.backendconfig.name](./values.yaml#L759) | string | backendconfig name | `nil` | +| [controller.backendconfig.spec](./values.yaml#L765) | object | backendconfig spec | `{}` | +| [controller.cloudName](./values.yaml#L487) | string | Name of default cloud configuration. | `"kubernetes"` | +| [controller.clusterIp](./values.yaml#L217) | string | k8s service clusterIP. Only used if serviceType is ClusterIP | `nil` | +| [controller.componentName](./values.yaml#L34) | string | Used for label app.kubernetes.io/component | `"jenkins-controller"` | +| [controller.containerEnv](./values.yaml#L150) | list | Environment variables for Jenkins Container | `[]` | +| [controller.containerEnvFrom](./values.yaml#L147) | list | Environment variable sources for Jenkins Container | `[]` | +| [controller.containerSecurityContext](./values.yaml#L205) | object | Allow controlling the securityContext for the jenkins container | `{"allowPrivilegeEscalation":false,"readOnlyRootFilesystem":true,"runAsGroup":1000,"runAsUser":1000}` | +| [controller.csrf.defaultCrumbIssuer.enabled](./values.yaml#L339) | bool | Enable the default CSRF Crumb issuer | `true` | +| [controller.csrf.defaultCrumbIssuer.proxyCompatability](./values.yaml#L341) | bool | Enable proxy compatibility | `true` | +| [controller.customInitContainers](./values.yaml#L541) | list | Custom init-container specification in raw-yaml format | `[]` | +| [controller.customJenkinsLabels](./values.yaml#L68) | list | Append Jenkins labels to the controller | `[]` | +| [controller.disableRememberMe](./values.yaml#L59) | bool | Disable use of remember me | `false` | +| [controller.disabledAgentProtocols](./values.yaml#L333) | list | Disabled agent protocols | `["JNLP-connect","JNLP2-connect"]` | +| [controller.enableRawHtmlMarkupFormatter](./values.yaml#L429) | bool | Enable HTML parsing using OWASP Markup Formatter Plugin (antisamy-markup-formatter) | `false` | +| [controller.executorMode](./values.yaml#L65) | string | Sets the executor mode of the Jenkins node. Possible values are "NORMAL" or "EXCLUSIVE" | `"NORMAL"` | +| [controller.existingSecret](./values.yaml#L456) | string | | `nil` | +| [controller.extraPorts](./values.yaml#L388) | list | Optionally configure other ports to expose in the controller container | `[]` | +| [controller.fsGroup](./values.yaml#L186) | int | Deprecated in favor of `controller.podSecurityContextOverride`. uid that will be used for persistent volume. | `1000` | +| [controller.googlePodMonitor.enabled](./values.yaml#L826) | bool | | `false` | +| [controller.googlePodMonitor.scrapeEndpoint](./values.yaml#L831) | string | | `"/prometheus"` | +| [controller.googlePodMonitor.scrapeInterval](./values.yaml#L829) | string | | `"60s"` | +| [controller.healthProbes](./values.yaml#L248) | bool | Enable Kubernetes Probes configuration configured in `controller.probes` | `true` | +| [controller.hostAliases](./values.yaml#L779) | list | Allows for adding entries to Pod /etc/hosts | `[]` | +| [controller.hostNetworking](./values.yaml#L70) | bool | | `false` | +| [controller.httpsKeyStore.disableSecretMount](./values.yaml#L847) | bool | | `false` | +| [controller.httpsKeyStore.enable](./values.yaml#L838) | bool | Enables HTTPS keystore on jenkins controller | `false` | +| [controller.httpsKeyStore.fileName](./values.yaml#L855) | string | Jenkins keystore filename which will appear under controller.httpsKeyStore.path | `"keystore.jks"` | +| [controller.httpsKeyStore.httpPort](./values.yaml#L851) | int | HTTP Port that Jenkins should listen to along with HTTPS, it also serves as the liveness and readiness probes port. | `8081` | +| [controller.httpsKeyStore.jenkinsHttpsJksPasswordSecretKey](./values.yaml#L846) | string | Name of the key in the secret that contains the JKS password | `"https-jks-password"` | +| [controller.httpsKeyStore.jenkinsHttpsJksPasswordSecretName](./values.yaml#L844) | string | Name of the secret that contains the JKS password, if it is not in the same secret as the JKS file | `""` | +| [controller.httpsKeyStore.jenkinsHttpsJksSecretKey](./values.yaml#L842) | string | Name of the key in the secret that already has ssl keystore | `"jenkins-jks-file"` | +| [controller.httpsKeyStore.jenkinsHttpsJksSecretName](./values.yaml#L840) | string | Name of the secret that already has ssl keystore | `""` | +| [controller.httpsKeyStore.jenkinsKeyStoreBase64Encoded](./values.yaml#L860) | string | Base64 encoded Keystore content. Keystore must be converted to base64 then being pasted here | `nil` | +| [controller.httpsKeyStore.password](./values.yaml#L857) | string | Jenkins keystore password | `"password"` | +| [controller.httpsKeyStore.path](./values.yaml#L853) | string | Path of HTTPS keystore file | `"/var/jenkins_keystore"` | +| [controller.image.pullPolicy](./values.yaml#L47) | string | Controller image pull policy | `"Always"` | +| [controller.image.registry](./values.yaml#L37) | string | Controller image registry | `"docker.io"` | +| [controller.image.repository](./values.yaml#L39) | string | Controller image repository | `"jenkins/jenkins"` | +| [controller.image.tag](./values.yaml#L42) | string | Controller image tag override; i.e., tag: "2.440.1-jdk17" | `nil` | +| [controller.image.tagLabel](./values.yaml#L45) | string | Controller image tag label | `"jdk17"` | +| [controller.imagePullSecretName](./values.yaml#L49) | string | Controller image pull secret | `nil` | +| [controller.ingress.annotations](./values.yaml#L702) | object | Ingress annotations | `{}` | +| [controller.ingress.apiVersion](./values.yaml#L698) | string | Ingress API version | `"extensions/v1beta1"` | +| [controller.ingress.enabled](./values.yaml#L681) | bool | Enables ingress | `false` | +| [controller.ingress.hostName](./values.yaml#L715) | string | Ingress hostname | `nil` | +| [controller.ingress.labels](./values.yaml#L700) | object | Ingress labels | `{}` | +| [controller.ingress.path](./values.yaml#L711) | string | Ingress path | `nil` | +| [controller.ingress.paths](./values.yaml#L685) | list | Override for the default Ingress paths | `[]` | +| [controller.ingress.resourceRootUrl](./values.yaml#L717) | string | Hostname to serve assets from | `nil` | +| [controller.ingress.tls](./values.yaml#L719) | list | Ingress TLS configuration | `[]` | +| [controller.initConfigMap](./values.yaml#L446) | string | Name of the existing ConfigMap that contains init scripts | `nil` | +| [controller.initContainerEnv](./values.yaml#L141) | list | Environment variables for Init Container | `[]` | +| [controller.initContainerEnvFrom](./values.yaml#L137) | list | Environment variable sources for Init Container | `[]` | +| [controller.initContainerResources](./values.yaml#L128) | object | Resources allocation (Requests and Limits) for Init Container | `{}` | +| [controller.initScripts](./values.yaml#L442) | object | Map of groovy init scripts to be executed during Jenkins controller start | `{}` | +| [controller.initializeOnce](./values.yaml#L414) | bool | Initialize only on first installation. Ensures plugins do not get updated inadvertently. Requires `persistence.enabled` to be set to `true` | `false` | +| [controller.installLatestPlugins](./values.yaml#L403) | bool | Download the minimum required version or latest version of all dependencies | `true` | +| [controller.installLatestSpecifiedPlugins](./values.yaml#L406) | bool | Set to true to download the latest version of any plugin that is requested to have the latest version | `false` | +| [controller.installPlugins](./values.yaml#L395) | list | List of Jenkins plugins to install. If you don't want to install plugins, set it to `false` | `["kubernetes:4253.v7700d91739e5","workflow-aggregator:600.vb_57cdd26fdd7","git:5.2.2","configuration-as-code:1836.vccda_4a_122a_a_e"]` | +| [controller.javaOpts](./values.yaml#L156) | string | Append to `JAVA_OPTS` env var | `nil` | +| [controller.jenkinsAdminEmail](./values.yaml#L96) | string | Email address for the administrator of the Jenkins instance | `nil` | +| [controller.jenkinsHome](./values.yaml#L101) | string | Custom Jenkins home path | `"/var/jenkins_home"` | +| [controller.jenkinsOpts](./values.yaml#L158) | string | Append to `JENKINS_OPTS` env var | `nil` | +| [controller.jenkinsRef](./values.yaml#L106) | string | Custom Jenkins reference path | `"/usr/share/jenkins/ref"` | +| [controller.jenkinsUriPrefix](./values.yaml#L173) | string | Root URI Jenkins will be served on | `nil` | +| [controller.jenkinsUrl](./values.yaml#L168) | string | Set Jenkins URL if you are not using the ingress definitions provided by the chart | `nil` | +| [controller.jenkinsUrlProtocol](./values.yaml#L165) | string | Set protocol for Jenkins URL; `https` if `controller.ingress.tls`, `http` otherwise | `nil` | +| [controller.jenkinsWar](./values.yaml#L109) | string | | `"/usr/share/jenkins/jenkins.war"` | +| [controller.jmxPort](./values.yaml#L385) | string | Open a port, for JMX stats | `nil` | +| [controller.legacyRemotingSecurityEnabled](./values.yaml#L361) | bool | Whether legacy remoting security should be enabled | `false` | +| [controller.lifecycle](./values.yaml#L51) | object | Lifecycle specification for controller-container | `{}` | +| [controller.loadBalancerIP](./values.yaml#L376) | string | Optionally assign a known public LB IP | `nil` | +| [controller.loadBalancerSourceRanges](./values.yaml#L372) | list | Allowed inbound IP addresses | `["0.0.0.0/0"]` | +| [controller.markupFormatter](./values.yaml#L433) | string | Yaml of the markup formatter to use | `"plainText"` | +| [controller.nodePort](./values.yaml#L223) | string | k8s node port. Only used if serviceType is NodePort | `nil` | +| [controller.nodeSelector](./values.yaml#L647) | object | Node labels for pod assignment | `{}` | +| [controller.numExecutors](./values.yaml#L62) | int | Set Number of executors | `0` | +| [controller.overwritePlugins](./values.yaml#L418) | bool | Overwrite installed plugins on start | `false` | +| [controller.overwritePluginsFromImage](./values.yaml#L422) | bool | Overwrite plugins that are already installed in the controller image | `true` | +| [controller.podAnnotations](./values.yaml#L668) | object | Annotations for controller pod | `{}` | +| [controller.podDisruptionBudget.annotations](./values.yaml#L312) | object | | `{}` | +| [controller.podDisruptionBudget.apiVersion](./values.yaml#L310) | string | Policy API version | `"policy/v1beta1"` | +| [controller.podDisruptionBudget.enabled](./values.yaml#L305) | bool | Enable Kubernetes Pod Disruption Budget configuration | `false` | +| [controller.podDisruptionBudget.labels](./values.yaml#L313) | object | | `{}` | +| [controller.podDisruptionBudget.maxUnavailable](./values.yaml#L315) | string | Number of pods that can be unavailable. Either an absolute number or a percentage | `"0"` | +| [controller.podLabels](./values.yaml#L241) | object | Custom Pod labels (an object with `label-key: label-value` pairs) | `{}` | +| [controller.podSecurityContextOverride](./values.yaml#L202) | string | Completely overwrites the contents of the pod security context, ignoring the values provided for `runAsUser`, `fsGroup`, and `securityContextCapabilities` | `nil` | +| [controller.priorityClassName](./values.yaml#L665) | string | The name of a `priorityClass` to apply to the controller pod | `nil` | +| [controller.probes.livenessProbe.failureThreshold](./values.yaml#L266) | int | Set the failure threshold for the liveness probe | `5` | +| [controller.probes.livenessProbe.httpGet.path](./values.yaml#L269) | string | Set the Pod's HTTP path for the liveness probe | `"{{ default \"\" .Values.controller.jenkinsUriPrefix }}/login"` | +| [controller.probes.livenessProbe.httpGet.port](./values.yaml#L271) | string | Set the Pod's HTTP port to use for the liveness probe | `"http"` | +| [controller.probes.livenessProbe.initialDelaySeconds](./values.yaml#L280) | string | Set the initial delay for the liveness probe in seconds | `nil` | +| [controller.probes.livenessProbe.periodSeconds](./values.yaml#L273) | int | Set the time interval between two liveness probes executions in seconds | `10` | +| [controller.probes.livenessProbe.timeoutSeconds](./values.yaml#L275) | int | Set the timeout for the liveness probe in seconds | `5` | +| [controller.probes.readinessProbe.failureThreshold](./values.yaml#L284) | int | Set the failure threshold for the readiness probe | `3` | +| [controller.probes.readinessProbe.httpGet.path](./values.yaml#L287) | string | Set the Pod's HTTP path for the liveness probe | `"{{ default \"\" .Values.controller.jenkinsUriPrefix }}/login"` | +| [controller.probes.readinessProbe.httpGet.port](./values.yaml#L289) | string | Set the Pod's HTTP port to use for the readiness probe | `"http"` | +| [controller.probes.readinessProbe.initialDelaySeconds](./values.yaml#L298) | string | Set the initial delay for the readiness probe in seconds | `nil` | +| [controller.probes.readinessProbe.periodSeconds](./values.yaml#L291) | int | Set the time interval between two readiness probes executions in seconds | `10` | +| [controller.probes.readinessProbe.timeoutSeconds](./values.yaml#L293) | int | Set the timeout for the readiness probe in seconds | `5` | +| [controller.probes.startupProbe.failureThreshold](./values.yaml#L253) | int | Set the failure threshold for the startup probe | `12` | +| [controller.probes.startupProbe.httpGet.path](./values.yaml#L256) | string | Set the Pod's HTTP path for the startup probe | `"{{ default \"\" .Values.controller.jenkinsUriPrefix }}/login"` | +| [controller.probes.startupProbe.httpGet.port](./values.yaml#L258) | string | Set the Pod's HTTP port to use for the startup probe | `"http"` | +| [controller.probes.startupProbe.periodSeconds](./values.yaml#L260) | int | Set the time interval between two startup probes executions in seconds | `10` | +| [controller.probes.startupProbe.timeoutSeconds](./values.yaml#L262) | int | Set the timeout for the startup probe in seconds | `5` | +| [controller.projectNamingStrategy](./values.yaml#L425) | string | | `"standard"` | +| [controller.prometheus.alertingRulesAdditionalLabels](./values.yaml#L812) | object | Additional labels to add to the PrometheusRule object | `{}` | +| [controller.prometheus.alertingrules](./values.yaml#L810) | list | Array of prometheus alerting rules | `[]` | +| [controller.prometheus.enabled](./values.yaml#L795) | bool | Enables prometheus service monitor | `false` | +| [controller.prometheus.metricRelabelings](./values.yaml#L822) | list | | `[]` | +| [controller.prometheus.prometheusRuleNamespace](./values.yaml#L814) | string | Set a custom namespace where to deploy PrometheusRule resource | `""` | +| [controller.prometheus.relabelings](./values.yaml#L820) | list | | `[]` | +| [controller.prometheus.scrapeEndpoint](./values.yaml#L805) | string | The endpoint prometheus should get metrics from | `"/prometheus"` | +| [controller.prometheus.scrapeInterval](./values.yaml#L801) | string | How often prometheus should scrape metrics | `"60s"` | +| [controller.prometheus.serviceMonitorAdditionalLabels](./values.yaml#L797) | object | Additional labels to add to the service monitor object | `{}` | +| [controller.prometheus.serviceMonitorNamespace](./values.yaml#L799) | string | Set a custom namespace where to deploy ServiceMonitor resource | `nil` | +| [controller.resources](./values.yaml#L115) | object | Resource allocation (Requests and Limits) | `{"limits":{"cpu":"2000m","memory":"4096Mi"},"requests":{"cpu":"50m","memory":"256Mi"}}` | +| [controller.route.annotations](./values.yaml#L774) | object | Route annotations | `{}` | +| [controller.route.enabled](./values.yaml#L770) | bool | Enables openshift route | `false` | +| [controller.route.labels](./values.yaml#L772) | object | Route labels | `{}` | +| [controller.route.path](./values.yaml#L776) | string | Route path | `nil` | +| [controller.runAsUser](./values.yaml#L183) | int | Deprecated in favor of `controller.podSecurityContextOverride`. uid that jenkins runs with. | `1000` | +| [controller.schedulerName](./values.yaml#L643) | string | Name of the Kubernetes scheduler to use | `""` | +| [controller.scriptApproval](./values.yaml#L437) | list | List of groovy functions to approve | `[]` | +| [controller.secondaryingress.annotations](./values.yaml#L737) | object | | `{}` | +| [controller.secondaryingress.apiVersion](./values.yaml#L735) | string | | `"extensions/v1beta1"` | +| [controller.secondaryingress.enabled](./values.yaml#L729) | bool | | `false` | +| [controller.secondaryingress.hostName](./values.yaml#L744) | string | | `nil` | +| [controller.secondaryingress.labels](./values.yaml#L736) | object | | `{}` | +| [controller.secondaryingress.paths](./values.yaml#L732) | list | | `[]` | +| [controller.secondaryingress.tls](./values.yaml#L745) | string | | `nil` | +| [controller.secretClaims](./values.yaml#L480) | list | List of `SecretClaim` resources to create | `[]` | +| [controller.securityContextCapabilities](./values.yaml#L192) | object | | `{}` | +| [controller.serviceAnnotations](./values.yaml#L230) | object | Jenkins controller service annotations | `{}` | +| [controller.serviceExternalTrafficPolicy](./values.yaml#L227) | string | | `nil` | +| [controller.serviceLabels](./values.yaml#L236) | object | Labels for the Jenkins controller-service | `{}` | +| [controller.servicePort](./values.yaml#L219) | int | k8s service port | `8080` | +| [controller.serviceType](./values.yaml#L214) | string | k8s service type | `"ClusterIP"` | +| [controller.shareProcessNamespace](./values.yaml#L124) | bool | | `false` | +| [controller.sidecars.additionalSidecarContainers](./values.yaml#L625) | list | Configures additional sidecar container(s) for the Jenkins controller | `[]` | +| [controller.sidecars.configAutoReload.additionalVolumeMounts](./values.yaml#L571) | list | Enables additional volume mounts for the config auto-reload container | `[]` | +| [controller.sidecars.configAutoReload.containerSecurityContext](./values.yaml#L620) | object | Enable container security context | `{"allowPrivilegeEscalation":false,"readOnlyRootFilesystem":true}` | +| [controller.sidecars.configAutoReload.enabled](./values.yaml#L554) | bool | Enables Jenkins Config as Code auto-reload | `true` | +| [controller.sidecars.configAutoReload.env](./values.yaml#L602) | object | Environment variables for the Jenkins Config as Code auto-reload container | `{}` | +| [controller.sidecars.configAutoReload.envFrom](./values.yaml#L600) | list | Environment variable sources for the Jenkins Config as Code auto-reload container | `[]` | +| [controller.sidecars.configAutoReload.folder](./values.yaml#L613) | string | | `"/var/jenkins_home/casc_configs"` | +| [controller.sidecars.configAutoReload.image.registry](./values.yaml#L557) | string | Registry for the image that triggers the reload | `"docker.io"` | +| [controller.sidecars.configAutoReload.image.repository](./values.yaml#L559) | string | Repository of the image that triggers the reload | `"kiwigrid/k8s-sidecar"` | +| [controller.sidecars.configAutoReload.image.tag](./values.yaml#L561) | string | Tag for the image that triggers the reload | `"1.27.5"` | +| [controller.sidecars.configAutoReload.imagePullPolicy](./values.yaml#L562) | string | | `"IfNotPresent"` | +| [controller.sidecars.configAutoReload.logging](./values.yaml#L577) | object | Config auto-reload logging settings | `{"configuration":{"backupCount":3,"formatter":"JSON","logLevel":"INFO","logToConsole":true,"logToFile":false,"maxBytes":1024,"override":false}}` | +| [controller.sidecars.configAutoReload.logging.configuration.override](./values.yaml#L581) | bool | Enables custom log config utilizing using the settings below. | `false` | +| [controller.sidecars.configAutoReload.reqRetryConnect](./values.yaml#L595) | int | How many connection-related errors to retry on | `10` | +| [controller.sidecars.configAutoReload.resources](./values.yaml#L563) | object | | `{}` | +| [controller.sidecars.configAutoReload.scheme](./values.yaml#L590) | string | The scheme to use when connecting to the Jenkins configuration as code endpoint | `"http"` | +| [controller.sidecars.configAutoReload.skipTlsVerify](./values.yaml#L592) | bool | Skip TLS verification when connecting to the Jenkins configuration as code endpoint | `false` | +| [controller.sidecars.configAutoReload.sleepTime](./values.yaml#L597) | string | How many seconds to wait before updating config-maps/secrets (sets METHOD=SLEEP on the sidecar) | `nil` | +| [controller.sidecars.configAutoReload.sshTcpPort](./values.yaml#L611) | int | | `1044` | +| [controller.statefulSetAnnotations](./values.yaml#L670) | object | Annotations for controller StatefulSet | `{}` | +| [controller.statefulSetLabels](./values.yaml#L232) | object | Jenkins controller custom labels for the StatefulSet | `{}` | +| [controller.targetPort](./values.yaml#L221) | int | k8s target port | `8080` | +| [controller.terminationGracePeriodSeconds](./values.yaml#L653) | string | Set TerminationGracePeriodSeconds | `nil` | +| [controller.terminationMessagePath](./values.yaml#L655) | string | Set the termination message path | `nil` | +| [controller.terminationMessagePolicy](./values.yaml#L657) | string | Set the termination message policy | `nil` | +| [controller.testEnabled](./values.yaml#L834) | bool | Can be used to disable rendering controller test resources when using helm template | `true` | +| [controller.tolerations](./values.yaml#L651) | list | Toleration labels for pod assignment | `[]` | +| [controller.topologySpreadConstraints](./values.yaml#L677) | object | Topology spread constraints | `{}` | +| [controller.updateStrategy](./values.yaml#L674) | object | Update strategy for StatefulSet | `{}` | +| [controller.usePodSecurityContext](./values.yaml#L176) | bool | Enable pod security context (must be `true` if podSecurityContextOverride, runAsUser or fsGroup are set) | `true` | +| [credentialsId](./values.yaml#L27) | string | The Jenkins credentials to access the Kubernetes API server. For the default cluster it is not needed. | `nil` | +| [fullnameOverride](./values.yaml#L13) | string | Override the full resource names | `jenkins-(release-name)` or `jenkins` if the release-name is `jenkins` | +| [helmtest.bats.image.registry](./values.yaml#L1333) | string | Registry of the image used to test the framework | `"docker.io"` | +| [helmtest.bats.image.repository](./values.yaml#L1335) | string | Repository of the image used to test the framework | `"bats/bats"` | +| [helmtest.bats.image.tag](./values.yaml#L1337) | string | Tag of the image to test the framework | `"1.11.0"` | +| [kubernetesURL](./values.yaml#L24) | string | The URL of the Kubernetes API server | `"https://kubernetes.default"` | +| [nameOverride](./values.yaml#L10) | string | Override the resource name prefix | `Chart.Name` | +| [namespaceOverride](./values.yaml#L16) | string | Override the deployment namespace | `Release.Namespace` | +| [networkPolicy.apiVersion](./values.yaml#L1263) | string | NetworkPolicy ApiVersion | `"networking.k8s.io/v1"` | +| [networkPolicy.enabled](./values.yaml#L1258) | bool | Enable the creation of NetworkPolicy resources | `false` | +| [networkPolicy.externalAgents.except](./values.yaml#L1277) | list | A list of IP sub-ranges to be excluded from the allowlisted IP range | `[]` | +| [networkPolicy.externalAgents.ipCIDR](./values.yaml#L1275) | string | The IP range from which external agents are allowed to connect to controller, i.e., 172.17.0.0/16 | `nil` | +| [networkPolicy.internalAgents.allowed](./values.yaml#L1267) | bool | Allow internal agents (from the same cluster) to connect to controller. Agent pods will be filtered based on PodLabels | `true` | +| [networkPolicy.internalAgents.namespaceLabels](./values.yaml#L1271) | object | A map of labels (keys/values) that agents namespaces must have to be able to connect to controller | `{}` | +| [networkPolicy.internalAgents.podLabels](./values.yaml#L1269) | object | A map of labels (keys/values) that agent pods must have to be able to connect to controller | `{}` | +| [persistence.accessMode](./values.yaml#L1233) | string | The PVC access mode | `"ReadWriteOnce"` | +| [persistence.annotations](./values.yaml#L1229) | object | Annotations for the PVC | `{}` | +| [persistence.dataSource](./values.yaml#L1239) | object | Existing data source to clone PVC from | `{}` | +| [persistence.enabled](./values.yaml#L1213) | bool | Enable the use of a Jenkins PVC | `true` | +| [persistence.existingClaim](./values.yaml#L1219) | string | Provide the name of a PVC | `nil` | +| [persistence.labels](./values.yaml#L1231) | object | Labels for the PVC | `{}` | +| [persistence.mounts](./values.yaml#L1251) | list | Additional mounts | `[]` | +| [persistence.size](./values.yaml#L1235) | string | The size of the PVC | `"8Gi"` | +| [persistence.storageClass](./values.yaml#L1227) | string | Storage class for the PVC | `nil` | +| [persistence.subPath](./values.yaml#L1244) | string | SubPath for jenkins-home mount | `nil` | +| [persistence.volumes](./values.yaml#L1246) | list | Additional volumes | `[]` | +| [rbac.create](./values.yaml#L1283) | bool | Whether RBAC resources are created | `true` | +| [rbac.readSecrets](./values.yaml#L1285) | bool | Whether the Jenkins service account should be able to read Kubernetes secrets | `false` | +| [renderHelmLabels](./values.yaml#L30) | bool | Enables rendering of the helm.sh/chart label to the annotations | `true` | +| [serviceAccount.annotations](./values.yaml#L1295) | object | Configures annotations for the ServiceAccount | `{}` | +| [serviceAccount.create](./values.yaml#L1289) | bool | Configures if a ServiceAccount with this name should be created | `true` | +| [serviceAccount.extraLabels](./values.yaml#L1297) | object | Configures extra labels for the ServiceAccount | `{}` | +| [serviceAccount.imagePullSecretName](./values.yaml#L1299) | string | Controller ServiceAccount image pull secret | `nil` | +| [serviceAccount.name](./values.yaml#L1293) | string | | `nil` | +| [serviceAccountAgent.annotations](./values.yaml#L1310) | object | Configures annotations for the agent ServiceAccount | `{}` | +| [serviceAccountAgent.create](./values.yaml#L1304) | bool | Configures if an agent ServiceAccount should be created | `false` | +| [serviceAccountAgent.extraLabels](./values.yaml#L1312) | object | Configures extra labels for the agent ServiceAccount | `{}` | +| [serviceAccountAgent.imagePullSecretName](./values.yaml#L1314) | string | Agent ServiceAccount image pull secret | `nil` | +| [serviceAccountAgent.name](./values.yaml#L1308) | string | The name of the agent ServiceAccount to be used by access-controlled resources | `nil` | diff --git a/charts/jenkins/jenkins/5.5.0/VALUES.md.gotmpl b/charts/jenkins/jenkins/5.5.0/VALUES.md.gotmpl new file mode 100644 index 000000000..21080e35a --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/VALUES.md.gotmpl @@ -0,0 +1,28 @@ +# Jenkins + +## Configuration + +The following tables list the configurable parameters of the Jenkins chart and their default values. + +{{- define "chart.valueDefaultColumnRender" -}} +{{- $defaultValue := (trimAll "`" (default .Default .AutoDefault) | replace "\n" "") -}} +`{{- $defaultValue | replace "\n" "" -}}` +{{- end -}} + +{{- define "chart.typeColumnRender" -}} +{{- .Type -}} +{{- end -}} + +{{- define "chart.valueDescription" -}} +{{- default .Description .AutoDescription }} +{{- end -}} + +{{- define "chart.valuesTable" -}} +| Key | Type | Description | Default | +|:----|:-----|:---------|:------------| +{{- range .Values }} +| [{{ .Key }}](./values.yaml#L{{ .LineNumber }}) | {{ template "chart.typeColumnRender" . }} | {{ template "chart.valueDescription" . }} | {{ template "chart.valueDefaultColumnRender" . }} | +{{- end }} +{{- end }} + +{{ template "chart.valuesSection" . }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/NOTES.txt b/charts/jenkins/jenkins/5.5.0/templates/NOTES.txt new file mode 100644 index 000000000..953dd2606 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/NOTES.txt @@ -0,0 +1,68 @@ +{{- $prefix := .Values.controller.jenkinsUriPrefix | default "" -}} +{{- $url := "" -}} +1. Get your '{{ .Values.controller.admin.username }}' user password by running: + kubectl exec --namespace {{ template "jenkins.namespace" . }} -it svc/{{ template "jenkins.fullname" . }} -c jenkins -- /bin/cat /run/secrets/additional/chart-admin-password && echo +{{- if .Values.controller.ingress.hostName -}} +{{- if .Values.controller.ingress.tls -}} +{{- $url = print "https://" .Values.controller.ingress.hostName $prefix -}} +{{- else -}} +{{- $url = print "http://" .Values.controller.ingress.hostName $prefix -}} +{{- end }} +2. Visit {{ $url }} +{{- else }} +2. Get the Jenkins URL to visit by running these commands in the same shell: +{{- if contains "NodePort" .Values.controller.serviceType }} + export NODE_PORT=$(kubectl get --namespace {{ template "jenkins.namespace" . }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "jenkins.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ template "jenkins.namespace" . }} -o jsonpath="{.items[0].status.addresses[0].address}") +{{- if .Values.controller.httpsKeyStore.enable -}} +{{- $url = print "https://$NODE_IP:$NODE_PORT" $prefix -}} +{{- else -}} +{{- $url = print "http://$NODE_IP:$NODE_PORT" $prefix -}} +{{- end }} + echo {{ $url }} + +{{- else if contains "LoadBalancer" .Values.controller.serviceType }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get svc --namespace {{ template "jenkins.namespace" . }} -w {{ template "jenkins.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ template "jenkins.namespace" . }} {{ template "jenkins.fullname" . }} --template "{{ "{{ range (index .status.loadBalancer.ingress 0) }}{{ . }}{{ end }}" }}") +{{- if .Values.controller.httpsKeyStore.enable -}} +{{- $url = print "https://$SERVICE_IP:" .Values.controller.servicePort $prefix -}} +{{- else -}} +{{- $url = print "http://$SERVICE_IP:" .Values.controller.servicePort $prefix -}} +{{- end }} + echo {{ $url }} + +{{- else if contains "ClusterIP" .Values.controller.serviceType -}} +{{- if .Values.controller.httpsKeyStore.enable -}} +{{- $url = print "https://127.0.0.1:" .Values.controller.servicePort $prefix -}} +{{- else -}} +{{- $url = print "http://127.0.0.1:" .Values.controller.servicePort $prefix -}} +{{- end }} + echo {{ $url }} + kubectl --namespace {{ template "jenkins.namespace" . }} port-forward svc/{{template "jenkins.fullname" . }} {{ .Values.controller.servicePort }}:{{ .Values.controller.servicePort }} +{{- end }} +{{- end }} + +3. Login with the password from step 1 and the username: {{ .Values.controller.admin.username }} +4. Configure security realm and authorization strategy +5. Use Jenkins Configuration as Code by specifying configScripts in your values.yaml file, see documentation: {{ $url }}/configuration-as-code and examples: https://github.com/jenkinsci/configuration-as-code-plugin/tree/master/demos + +For more information on running Jenkins on Kubernetes, visit: +https://cloud.google.com/solutions/jenkins-on-container-engine + +For more information about Jenkins Configuration as Code, visit: +https://jenkins.io/projects/jcasc/ + +{{ if and (eq .Values.controller.image.repository "jenkins/jenkins") (eq .Values.controller.image.registry "docker.io") }} +NOTE: Consider using a custom image with pre-installed plugins +{{- else if .Values.controller.installPlugins }} +NOTE: Consider disabling `installPlugins` if your image already contains plugins. +{{- end }} + +{{- if .Values.persistence.enabled }} +{{- else }} +################################################################################# +###### WARNING: Persistence is disabled!!! You will lose your data when ##### +###### the Jenkins pod is terminated. ##### +################################################################################# +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/_helpers.tpl b/charts/jenkins/jenkins/5.5.0/templates/_helpers.tpl new file mode 100644 index 000000000..fef2bf585 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/_helpers.tpl @@ -0,0 +1,673 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "jenkins.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Expand the label of the chart. +*/}} +{{- define "jenkins.label" -}} +{{- printf "%s-%s" (include "jenkins.name" .) .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + + +{{/* +Allow the release namespace to be overridden for multi-namespace deployments in combined charts. +*/}} +{{- define "jenkins.namespace" -}} + {{- if .Values.namespaceOverride -}} + {{- .Values.namespaceOverride -}} + {{- else -}} + {{- .Release.Namespace -}} + {{- end -}} +{{- end -}} + +{{- define "jenkins.agent.namespace" -}} + {{- if .Values.agent.namespace -}} + {{- tpl .Values.agent.namespace . -}} + {{- else -}} + {{- if .Values.namespaceOverride -}} + {{- .Values.namespaceOverride -}} + {{- else -}} + {{- .Release.Namespace -}} + {{- end -}} + {{- end -}} +{{- end -}} + + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "jenkins.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Returns the admin password +https://github.com/helm/charts/issues/5167#issuecomment-619137759 +*/}} +{{- define "jenkins.password" -}} + {{- if .Values.controller.admin.password -}} + {{- .Values.controller.admin.password | b64enc | quote }} + {{- else -}} + {{- $secret := (lookup "v1" "Secret" .Release.Namespace (include "jenkins.fullname" .)).data -}} + {{- if $secret -}} + {{/* + Reusing current password since secret exists + */}} + {{- index $secret ( .Values.controller.admin.passwordKey | default "jenkins-admin-password" ) -}} + {{- else -}} + {{/* + Generate new password + */}} + {{- randAlphaNum 22 | b64enc | quote }} + {{- end -}} + {{- end -}} +{{- end -}} + +{{/* +Returns the Jenkins URL +*/}} +{{- define "jenkins.url" -}} +{{- if .Values.controller.jenkinsUrl }} + {{- .Values.controller.jenkinsUrl }} +{{- else }} + {{- if .Values.controller.ingress.hostName }} + {{- if .Values.controller.ingress.tls }} + {{- default "https" .Values.controller.jenkinsUrlProtocol }}://{{ tpl .Values.controller.ingress.hostName $ }}{{ default "" .Values.controller.jenkinsUriPrefix }} + {{- else }} + {{- default "http" .Values.controller.jenkinsUrlProtocol }}://{{ tpl .Values.controller.ingress.hostName $ }}{{ default "" .Values.controller.jenkinsUriPrefix }} + {{- end }} + {{- else }} + {{- default "http" .Values.controller.jenkinsUrlProtocol }}://{{ template "jenkins.fullname" . }}:{{.Values.controller.servicePort}}{{ default "" .Values.controller.jenkinsUriPrefix }} + {{- end}} +{{- end}} +{{- end -}} + +{{/* +Returns configuration as code default config +*/}} +{{- define "jenkins.casc.defaults" -}} +jenkins: + {{- $configScripts := toYaml .Values.controller.JCasC.configScripts }} + {{- if and (.Values.controller.JCasC.authorizationStrategy) (not (contains "authorizationStrategy:" $configScripts)) }} + authorizationStrategy: + {{- tpl .Values.controller.JCasC.authorizationStrategy . | nindent 4 }} + {{- end }} + {{- if and (.Values.controller.JCasC.securityRealm) (not (contains "securityRealm:" $configScripts)) }} + securityRealm: + {{- tpl .Values.controller.JCasC.securityRealm . | nindent 4 }} + {{- end }} + disableRememberMe: {{ .Values.controller.disableRememberMe }} + {{- if .Values.controller.legacyRemotingSecurityEnabled }} + remotingSecurity: + enabled: true + {{- end }} + mode: {{ .Values.controller.executorMode }} + numExecutors: {{ .Values.controller.numExecutors }} + {{- if not (kindIs "invalid" .Values.controller.customJenkinsLabels) }} + labelString: "{{ join " " .Values.controller.customJenkinsLabels }}" + {{- end }} + {{- if .Values.controller.projectNamingStrategy }} + {{- if kindIs "string" .Values.controller.projectNamingStrategy }} + projectNamingStrategy: "{{ .Values.controller.projectNamingStrategy }}" + {{- else }} + projectNamingStrategy: + {{- toYaml .Values.controller.projectNamingStrategy | nindent 4 }} + {{- end }} + {{- end }} + markupFormatter: + {{- if .Values.controller.enableRawHtmlMarkupFormatter }} + rawHtml: + disableSyntaxHighlighting: true + {{- else }} + {{- toYaml .Values.controller.markupFormatter | nindent 4 }} + {{- end }} + clouds: + - kubernetes: + containerCapStr: "{{ .Values.agent.containerCap }}" + {{- if .Values.agent.jnlpregistry }} + jnlpregistry: "{{ .Values.agent.jnlpregistry }}" + {{- end }} + defaultsProviderTemplate: "{{ .Values.agent.defaultsProviderTemplate }}" + connectTimeout: "{{ .Values.agent.kubernetesConnectTimeout }}" + readTimeout: "{{ .Values.agent.kubernetesReadTimeout }}" + {{- if .Values.agent.directConnection }} + directConnection: true + {{- else }} + {{- if .Values.agent.jenkinsUrl }} + jenkinsUrl: "{{ tpl .Values.agent.jenkinsUrl . }}" + {{- else }} + jenkinsUrl: "http://{{ template "jenkins.fullname" . }}.{{ template "jenkins.namespace" . }}.svc.{{.Values.clusterZone}}:{{.Values.controller.servicePort}}{{ default "" .Values.controller.jenkinsUriPrefix }}" + {{- end }} + {{- if not .Values.agent.websocket }} + {{- if .Values.agent.jenkinsTunnel }} + jenkinsTunnel: "{{ tpl .Values.agent.jenkinsTunnel . }}" + {{- else }} + jenkinsTunnel: "{{ template "jenkins.fullname" . }}-agent.{{ template "jenkins.namespace" . }}.svc.{{.Values.clusterZone}}:{{ .Values.controller.agentListenerPort }}" + {{- end }} + {{- else }} + webSocket: true + {{- end }} + {{- end }} + skipTlsVerify: {{ .Values.agent.skipTlsVerify | default false}} + usageRestricted: {{ .Values.agent.usageRestricted | default false}} + maxRequestsPerHostStr: {{ .Values.agent.maxRequestsPerHostStr | quote }} + retentionTimeout: {{ .Values.agent.retentionTimeout | quote }} + waitForPodSec: {{ .Values.agent.waitForPodSec | quote }} + name: "{{ .Values.controller.cloudName }}" + namespace: "{{ template "jenkins.agent.namespace" . }}" + restrictedPssSecurityContext: {{ .Values.agent.restrictedPssSecurityContext }} + serverUrl: "{{ .Values.kubernetesURL }}" + credentialsId: "{{ .Values.credentialsId }}" + {{- if .Values.agent.enabled }} + podLabels: + - key: "jenkins/{{ .Release.Name }}-{{ .Values.agent.componentName }}" + value: "true" + {{- range $key, $val := .Values.agent.podLabels }} + - key: {{ $key | quote }} + value: {{ $val | quote }} + {{- end }} + templates: + {{- if not .Values.agent.disableDefaultAgent }} + {{- include "jenkins.casc.podTemplate" . | nindent 8 }} + {{- end }} + {{- if .Values.additionalAgents }} + {{- /* save .Values.agent */}} + {{- $agent := .Values.agent }} + {{- range $name, $additionalAgent := .Values.additionalAgents }} + {{- $additionalContainersEmpty := and (hasKey $additionalAgent "additionalContainers") (empty $additionalAgent.additionalContainers) }} + {{- /* merge original .Values.agent into additional agent to ensure it at least has the default values */}} + {{- $additionalAgent := merge $additionalAgent $agent }} + {{- /* clear list of additional containers in case it is configured empty for this agent (merge might have overwritten that) */}} + {{- if $additionalContainersEmpty }} + {{- $_ := set $additionalAgent "additionalContainers" list }} + {{- end }} + {{- /* set .Values.agent to $additionalAgent */}} + {{- $_ := set $.Values "agent" $additionalAgent }} + {{- include "jenkins.casc.podTemplate" $ | nindent 8 }} + {{- end }} + {{- /* restore .Values.agent */}} + {{- $_ := set .Values "agent" $agent }} + {{- end }} + {{- if .Values.agent.podTemplates }} + {{- range $key, $val := .Values.agent.podTemplates }} + {{- tpl $val $ | nindent 8 }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.additionalClouds }} + {{- /* save root */}} + {{- $oldRoot := deepCopy $ }} + {{- range $name, $additionalCloud := .Values.additionalClouds }} + {{- $newRoot := deepCopy $ }} + {{- /* clear additionalAgents from the copy if override set to `true` */}} + {{- if .additionalAgentsOverride }} + {{- $_ := set $newRoot.Values "additionalAgents" list}} + {{- end}} + {{- $newValues := merge $additionalCloud $newRoot.Values }} + {{- $_ := set $newRoot "Values" $newValues }} + {{- /* clear additionalClouds from the copy */}} + {{- $_ := set $newRoot.Values "additionalClouds" list }} + {{- with $newRoot}} + - kubernetes: + containerCapStr: "{{ .Values.agent.containerCap }}" + {{- if .Values.agent.jnlpregistry }} + jnlpregistry: "{{ .Values.agent.jnlpregistry }}" + {{- end }} + defaultsProviderTemplate: "{{ .Values.agent.defaultsProviderTemplate }}" + connectTimeout: "{{ .Values.agent.kubernetesConnectTimeout }}" + readTimeout: "{{ .Values.agent.kubernetesReadTimeout }}" + {{- if .Values.agent.directConnection }} + directConnection: true + {{- else }} + {{- if .Values.agent.jenkinsUrl }} + jenkinsUrl: "{{ tpl .Values.agent.jenkinsUrl . }}" + {{- else }} + jenkinsUrl: "http://{{ template "jenkins.fullname" . }}.{{ template "jenkins.namespace" . }}.svc.{{.Values.clusterZone}}:{{.Values.controller.servicePort}}{{ default "" .Values.controller.jenkinsUriPrefix }}" + {{- end }} + {{- if not .Values.agent.websocket }} + {{- if .Values.agent.jenkinsTunnel }} + jenkinsTunnel: "{{ tpl .Values.agent.jenkinsTunnel . }}" + {{- else }} + jenkinsTunnel: "{{ template "jenkins.fullname" . }}-agent.{{ template "jenkins.namespace" . }}.svc.{{.Values.clusterZone}}:{{ .Values.controller.agentListenerPort }}" + {{- end }} + {{- else }} + webSocket: true + {{- end }} + {{- end }} + skipTlsVerify: {{ .Values.agent.skipTlsVerify | default false}} + usageRestricted: {{ .Values.agent.usageRestricted | default false}} + maxRequestsPerHostStr: {{ .Values.agent.maxRequestsPerHostStr | quote }} + retentionTimeout: {{ .Values.agent.retentionTimeout | quote }} + waitForPodSec: {{ .Values.agent.waitForPodSec | quote }} + name: {{ $name | quote }} + namespace: "{{ template "jenkins.agent.namespace" . }}" + restrictedPssSecurityContext: {{ .Values.agent.restrictedPssSecurityContext }} + serverUrl: "{{ .Values.kubernetesURL }}" + credentialsId: "{{ .Values.credentialsId }}" + {{- if .Values.agent.enabled }} + podLabels: + - key: "jenkins/{{ .Release.Name }}-{{ .Values.agent.componentName }}" + value: "true" + {{- range $key, $val := .Values.agent.podLabels }} + - key: {{ $key | quote }} + value: {{ $val | quote }} + {{- end }} + templates: + {{- if not .Values.agent.disableDefaultAgent }} + {{- include "jenkins.casc.podTemplate" . | nindent 8 }} + {{- end }} + {{- if .Values.additionalAgents }} + {{- /* save .Values.agent */}} + {{- $agent := .Values.agent }} + {{- range $name, $additionalAgent := .Values.additionalAgents }} + {{- $additionalContainersEmpty := and (hasKey $additionalAgent "additionalContainers") (empty $additionalAgent.additionalContainers) }} + {{- /* merge original .Values.agent into additional agent to ensure it at least has the default values */}} + {{- $additionalAgent := merge $additionalAgent $agent }} + {{- /* clear list of additional containers in case it is configured empty for this agent (merge might have overwritten that) */}} + {{- if $additionalContainersEmpty }} + {{- $_ := set $additionalAgent "additionalContainers" list }} + {{- end }} + {{- /* set .Values.agent to $additionalAgent */}} + {{- $_ := set $.Values "agent" $additionalAgent }} + {{- include "jenkins.casc.podTemplate" $ | nindent 8 }} + {{- end }} + {{- /* restore .Values.agent */}} + {{- $_ := set .Values "agent" $agent }} + {{- end }} + {{- with .Values.agent.podTemplates }} + {{- range $key, $val := . }} + {{- tpl $val $ | nindent 8 }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- /* restore root */}} + {{- $_ := set $ "Values" $oldRoot.Values }} + {{- end }} + {{- if .Values.controller.csrf.defaultCrumbIssuer.enabled }} + crumbIssuer: + standard: + excludeClientIPFromCrumb: {{ if .Values.controller.csrf.defaultCrumbIssuer.proxyCompatability }}true{{ else }}false{{- end }} + {{- end }} +{{- include "jenkins.casc.security" . }} +{{- with .Values.controller.scriptApproval }} + scriptApproval: + approvedSignatures: + {{- range $key, $val := . }} + - "{{ $val }}" + {{- end }} +{{- end }} +unclassified: + location: + {{- with .Values.controller.jenkinsAdminEmail }} + adminAddress: {{ . }} + {{- end }} + url: {{ template "jenkins.url" . }} +{{- end -}} + +{{/* +Returns a name template to be used for jcasc configmaps, using +suffix passed in at call as index 0 +*/}} +{{- define "jenkins.casc.configName" -}} +{{- $name := index . 0 -}} +{{- $root := index . 1 -}} +"{{- include "jenkins.fullname" $root -}}-jenkins-{{ $name }}" +{{- end -}} + +{{/* +Returns kubernetes pod template configuration as code +*/}} +{{- define "jenkins.casc.podTemplate" -}} +- name: "{{ .Values.agent.podName }}" + namespace: "{{ template "jenkins.agent.namespace" . }}" +{{- if .Values.agent.annotations }} + annotations: + {{- range $key, $value := .Values.agent.annotations }} + - key: {{ $key }} + value: {{ $value | quote }} + {{- end }} +{{- end }} + id: {{ sha256sum (toYaml .Values.agent) }} + containers: + - name: "{{ .Values.agent.sideContainerName }}" + alwaysPullImage: {{ .Values.agent.alwaysPullImage }} + args: "{{ .Values.agent.args | replace "$" "^$" }}" + {{- with .Values.agent.command }} + command: {{ . }} + {{- end }} + envVars: + - envVar: + {{- if .Values.agent.directConnection }} + key: "JENKINS_DIRECT_CONNECTION" + {{- if .Values.agent.jenkinsTunnel }} + value: "{{ tpl .Values.agent.jenkinsTunnel . }}" + {{- else }} + value: "{{ template "jenkins.fullname" . }}-agent.{{ template "jenkins.namespace" . }}.svc.{{.Values.clusterZone}}:{{ .Values.controller.agentListenerPort }}" + {{- end }} + {{- else }} + key: "JENKINS_URL" + {{- if .Values.agent.jenkinsUrl }} + value: {{ tpl .Values.agent.jenkinsUrl . }} + {{- else }} + value: "http://{{ template "jenkins.fullname" . }}.{{ template "jenkins.namespace" . }}.svc.{{.Values.clusterZone}}:{{.Values.controller.servicePort}}{{ default "/" .Values.controller.jenkinsUriPrefix }}" + {{- end }} + {{- end }} + image: "{{ .Values.agent.image.repository }}:{{ .Values.agent.image.tag }}" + {{- if .Values.agent.livenessProbe }} + livenessProbe: + execArgs: {{.Values.agent.livenessProbe.execArgs | quote}} + failureThreshold: {{.Values.agent.livenessProbe.failureThreshold}} + initialDelaySeconds: {{.Values.agent.livenessProbe.initialDelaySeconds}} + periodSeconds: {{.Values.agent.livenessProbe.periodSeconds}} + successThreshold: {{.Values.agent.livenessProbe.successThreshold}} + timeoutSeconds: {{.Values.agent.livenessProbe.timeoutSeconds}} + {{- end }} + privileged: "{{- if .Values.agent.privileged }}true{{- else }}false{{- end }}" + resourceLimitCpu: {{.Values.agent.resources.limits.cpu}} + resourceLimitMemory: {{.Values.agent.resources.limits.memory}} + {{- with .Values.agent.resources.limits.ephemeralStorage }} + resourceLimitEphemeralStorage: {{.}} + {{- end }} + resourceRequestCpu: {{.Values.agent.resources.requests.cpu}} + resourceRequestMemory: {{.Values.agent.resources.requests.memory}} + {{- with .Values.agent.resources.requests.ephemeralStorage }} + resourceRequestEphemeralStorage: {{.}} + {{- end }} + {{- with .Values.agent.runAsUser }} + runAsUser: {{ . }} + {{- end }} + {{- with .Values.agent.runAsGroup }} + runAsGroup: {{ . }} + {{- end }} + ttyEnabled: {{ .Values.agent.TTYEnabled }} + workingDir: {{ .Values.agent.workingDir }} +{{- range $additionalContainers := .Values.agent.additionalContainers }} + - name: "{{ $additionalContainers.sideContainerName }}" + alwaysPullImage: {{ $additionalContainers.alwaysPullImage | default $.Values.agent.alwaysPullImage }} + args: "{{ $additionalContainers.args | replace "$" "^$" }}" + {{- with $additionalContainers.command }} + command: {{ . }} + {{- end }} + envVars: + - envVar: + key: "JENKINS_URL" + {{- if $additionalContainers.jenkinsUrl }} + value: {{ tpl ($additionalContainers.jenkinsUrl) . }} + {{- else }} + value: "http://{{ template "jenkins.fullname" $ }}.{{ template "jenkins.namespace" $ }}.svc.{{ $.Values.clusterZone }}:{{ $.Values.controller.servicePort }}{{ default "/" $.Values.controller.jenkinsUriPrefix }}" + {{- end }} + image: "{{ $additionalContainers.image.repository }}:{{ $additionalContainers.image.tag }}" + {{- if $additionalContainers.livenessProbe }} + livenessProbe: + execArgs: {{$additionalContainers.livenessProbe.execArgs | quote}} + failureThreshold: {{$additionalContainers.livenessProbe.failureThreshold}} + initialDelaySeconds: {{$additionalContainers.livenessProbe.initialDelaySeconds}} + periodSeconds: {{$additionalContainers.livenessProbe.periodSeconds}} + successThreshold: {{$additionalContainers.livenessProbe.successThreshold}} + timeoutSeconds: {{$additionalContainers.livenessProbe.timeoutSeconds}} + {{- end }} + privileged: "{{- if $additionalContainers.privileged }}true{{- else }}false{{- end }}" + resourceLimitCpu: {{ if $additionalContainers.resources }}{{ $additionalContainers.resources.limits.cpu }}{{ else }}{{ $.Values.agent.resources.limits.cpu }}{{ end }} + resourceLimitMemory: {{ if $additionalContainers.resources }}{{ $additionalContainers.resources.limits.memory }}{{ else }}{{ $.Values.agent.resources.limits.memory }}{{ end }} + resourceRequestCpu: {{ if $additionalContainers.resources }}{{ $additionalContainers.resources.requests.cpu }}{{ else }}{{ $.Values.agent.resources.requests.cpu }}{{ end }} + resourceRequestMemory: {{ if $additionalContainers.resources }}{{ $additionalContainers.resources.requests.memory }}{{ else }}{{ $.Values.agent.resources.requests.memory }}{{ end }} + {{- if or $additionalContainers.runAsUser $.Values.agent.runAsUser }} + runAsUser: {{ $additionalContainers.runAsUser | default $.Values.agent.runAsUser }} + {{- end }} + {{- if or $additionalContainers.runAsGroup $.Values.agent.runAsGroup }} + runAsGroup: {{ $additionalContainers.runAsGroup | default $.Values.agent.runAsGroup }} + {{- end }} + ttyEnabled: {{ $additionalContainers.TTYEnabled | default $.Values.agent.TTYEnabled }} + workingDir: {{ $additionalContainers.workingDir | default $.Values.agent.workingDir }} +{{- end }} +{{- if or .Values.agent.envVars .Values.agent.secretEnvVars }} + envVars: + {{- range $index, $var := .Values.agent.envVars }} + - envVar: + key: {{ $var.name }} + value: {{ tpl $var.value $ }} + {{- end }} + {{- range $index, $var := .Values.agent.secretEnvVars }} + - secretEnvVar: + key: {{ $var.key }} + secretName: {{ $var.secretName }} + secretKey: {{ $var.secretKey }} + optional: {{ $var.optional | default false }} + {{- end }} +{{- end }} + idleMinutes: {{ .Values.agent.idleMinutes }} + instanceCap: 2147483647 + {{- if .Values.agent.hostNetworking }} + hostNetwork: {{ .Values.agent.hostNetworking }} + {{- end }} + {{- if .Values.agent.imagePullSecretName }} + imagePullSecrets: + - name: {{ .Values.agent.imagePullSecretName }} + {{- end }} + label: "{{ .Release.Name }}-{{ .Values.agent.componentName }} {{ .Values.agent.customJenkinsLabels | join " " }}" +{{- if .Values.agent.nodeSelector }} + nodeSelector: + {{- $local := dict "first" true }} + {{- range $key, $value := .Values.agent.nodeSelector }} + {{- if $local.first }} {{ else }},{{ end }} + {{- $key }}={{ tpl $value $ }} + {{- $_ := set $local "first" false }} + {{- end }} +{{- end }} + nodeUsageMode: {{ quote .Values.agent.nodeUsageMode }} + podRetention: {{ .Values.agent.podRetention }} + showRawYaml: {{ .Values.agent.showRawYaml }} + serviceAccount: "{{ include "jenkins.serviceAccountAgentName" . }}" + slaveConnectTimeoutStr: "{{ .Values.agent.connectTimeout }}" +{{- if .Values.agent.volumes }} + volumes: + {{- range $index, $volume := .Values.agent.volumes }} + -{{- if (eq $volume.type "ConfigMap") }} configMapVolume: + {{- else if (eq $volume.type "EmptyDir") }} emptyDirVolume: + {{- else if (eq $volume.type "EphemeralVolume") }} genericEphemeralVolume: + {{- else if (eq $volume.type "HostPath") }} hostPathVolume: + {{- else if (eq $volume.type "Nfs") }} nfsVolume: + {{- else if (eq $volume.type "PVC") }} persistentVolumeClaim: + {{- else if (eq $volume.type "Secret") }} secretVolume: + {{- else }} {{ $volume.type }}: + {{- end }} + {{- range $key, $value := $volume }} + {{- if not (eq $key "type") }} + {{ $key }}: {{ if kindIs "string" $value }}{{ tpl $value $ | quote }}{{ else }}{{ $value }}{{ end }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} +{{- if .Values.agent.workspaceVolume }} + workspaceVolume: + {{- if (eq .Values.agent.workspaceVolume.type "DynamicPVC") }} + dynamicPVC: + {{- else if (eq .Values.agent.workspaceVolume.type "EmptyDir") }} + emptyDirWorkspaceVolume: + {{- else if (eq .Values.agent.workspaceVolume.type "EphemeralVolume") }} + genericEphemeralVolume: + {{- else if (eq .Values.agent.workspaceVolume.type "HostPath") }} + hostPathWorkspaceVolume: + {{- else if (eq .Values.agent.workspaceVolume.type "Nfs") }} + nfsWorkspaceVolume: + {{- else if (eq .Values.agent.workspaceVolume.type "PVC") }} + persistentVolumeClaimWorkspaceVolume: + {{- else }} + {{ .Values.agent.workspaceVolume.type }}: + {{- end }} + {{- range $key, $value := .Values.agent.workspaceVolume }} + {{- if not (eq $key "type") }} + {{ $key }}: {{ if kindIs "string" $value }}{{ tpl $value $ | quote }}{{ else }}{{ $value }}{{ end }} + {{- end }} + {{- end }} +{{- end }} +{{- if .Values.agent.yamlTemplate }} + yaml: |- + {{- tpl (trim .Values.agent.yamlTemplate) . | nindent 4 }} +{{- end }} + yamlMergeStrategy: {{ .Values.agent.yamlMergeStrategy }} + inheritYamlMergeStrategy: {{ .Values.agent.inheritYamlMergeStrategy }} +{{- end -}} + +{{- define "jenkins.kubernetes-version" -}} + {{- if .Values.controller.installPlugins -}} + {{- range .Values.controller.installPlugins -}} + {{- if hasPrefix "kubernetes:" . }} + {{- $split := splitList ":" . }} + {{- printf "%s" (index $split 1 ) -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- define "jenkins.casc.security" }} +security: +{{- with .Values.controller.JCasC }} +{{- if .security }} + {{- .security | toYaml | nindent 2 }} +{{- end }} +{{- end }} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "jenkins.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "jenkins.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the service account for Jenkins agents to use +*/}} +{{- define "jenkins.serviceAccountAgentName" -}} +{{- if .Values.serviceAccountAgent.create -}} + {{ default (printf "%s-%s" (include "jenkins.fullname" .) "agent") .Values.serviceAccountAgent.name }} +{{- else -}} + {{ default "default" .Values.serviceAccountAgent.name }} +{{- end -}} +{{- end -}} + +{{/* +Create a full tag name for controller image +*/}} +{{- define "controller.image.tag" -}} +{{- if .Values.controller.image.tagLabel -}} + {{- default (printf "%s-%s" .Chart.AppVersion .Values.controller.image.tagLabel) .Values.controller.image.tag -}} +{{- else -}} + {{- default .Chart.AppVersion .Values.controller.image.tag -}} +{{- end -}} +{{- end -}} + +{{/* +Create the HTTP port for interacting with the controller +*/}} +{{- define "controller.httpPort" -}} +{{- if .Values.controller.httpsKeyStore.enable -}} + {{- .Values.controller.httpsKeyStore.httpPort -}} +{{- else -}} + {{- .Values.controller.targetPort -}} +{{- end -}} +{{- end -}} + +{{- define "jenkins.configReloadContainer" -}} +{{- $root := index . 0 -}} +{{- $containerName := index . 1 -}} +{{- $containerType := index . 2 -}} +- name: {{ $containerName }} + image: "{{ $root.Values.controller.sidecars.configAutoReload.image.registry }}/{{ $root.Values.controller.sidecars.configAutoReload.image.repository }}:{{ $root.Values.controller.sidecars.configAutoReload.image.tag }}" + imagePullPolicy: {{ $root.Values.controller.sidecars.configAutoReload.imagePullPolicy }} + {{- if $root.Values.controller.sidecars.configAutoReload.containerSecurityContext }} + securityContext: {{- toYaml $root.Values.controller.sidecars.configAutoReload.containerSecurityContext | nindent 4 }} + {{- end }} + {{- if $root.Values.controller.sidecars.configAutoReload.envFrom }} + envFrom: +{{ (tpl (toYaml $root.Values.controller.sidecars.configAutoReload.envFrom) $root) | indent 4 }} + {{- end }} + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: LABEL + value: "{{ template "jenkins.fullname" $root }}-jenkins-config" + - name: FOLDER + value: "{{ $root.Values.controller.sidecars.configAutoReload.folder }}" + - name: NAMESPACE + value: '{{ $root.Values.controller.sidecars.configAutoReload.searchNamespace | default (include "jenkins.namespace" $root) }}' + {{- if eq $containerType "init" }} + - name: METHOD + value: "LIST" + {{- else if $root.Values.controller.sidecars.configAutoReload.sleepTime }} + - name: METHOD + value: "SLEEP" + - name: SLEEP_TIME + value: "{{ $root.Values.controller.sidecars.configAutoReload.sleepTime }}" + {{- end }} + {{- if eq $containerType "sidecar" }} + - name: REQ_URL + value: "{{- default "http" $root.Values.controller.sidecars.configAutoReload.scheme }}://localhost:{{- include "controller.httpPort" $root -}}{{- $root.Values.controller.jenkinsUriPrefix -}}/reload-configuration-as-code/?casc-reload-token=$(POD_NAME)" + - name: REQ_METHOD + value: "POST" + - name: REQ_RETRY_CONNECT + value: "{{ $root.Values.controller.sidecars.configAutoReload.reqRetryConnect }}" + {{- if $root.Values.controller.sidecars.configAutoReload.skipTlsVerify }} + - name: REQ_SKIP_TLS_VERIFY + value: "true" + {{- end }} + {{- end }} + + {{- if $root.Values.controller.sidecars.configAutoReload.env }} + {{- range $envVarItem := $root.Values.controller.sidecars.configAutoReload.env -}} + {{- if or (ne $containerType "init") (ne .name "METHOD") }} +{{- (tpl (toYaml (list $envVarItem)) $root) | nindent 4 }} + {{- end -}} + {{- end -}} + {{- end }} + {{- if $root.Values.controller.sidecars.configAutoReload.logging.configuration.override }} + - name: LOG_CONFIG + value: "{{ $root.Values.controller.jenkinsHome }}/auto-reload/auto-reload-config.yaml" + {{- end }} + + resources: +{{ toYaml $root.Values.controller.sidecars.configAutoReload.resources | indent 4 }} + volumeMounts: + - name: sc-config-volume + mountPath: {{ $root.Values.controller.sidecars.configAutoReload.folder | quote }} + - name: jenkins-home + mountPath: {{ $root.Values.controller.jenkinsHome }} + {{- if $root.Values.persistence.subPath }} + subPath: {{ $root.Values.persistence.subPath }} + {{- end }} + {{- if $root.Values.controller.sidecars.configAutoReload.logging.configuration.override }} + - name: auto-reload-config + mountPath: {{ $root.Values.controller.jenkinsHome }}/auto-reload + - name: auto-reload-config-logs + mountPath: {{ $root.Values.controller.jenkinsHome }}/auto-reload-logs + {{- end }} + {{- if $root.Values.controller.sidecars.configAutoReload.additionalVolumeMounts }} +{{ (tpl (toYaml $root.Values.controller.sidecars.configAutoReload.additionalVolumeMounts) $root) | indent 4 }} + {{- end }} + +{{- end -}} diff --git a/charts/jenkins/jenkins/5.5.0/templates/auto-reload-config.yaml b/charts/jenkins/jenkins/5.5.0/templates/auto-reload-config.yaml new file mode 100644 index 000000000..8c177d7f3 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/auto-reload-config.yaml @@ -0,0 +1,60 @@ +{{- if .Values.controller.sidecars.configAutoReload.logging.configuration.override }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "jenkins.fullname" . }}-auto-reload-config + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": {{ template "jenkins.name" . }} + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ .Chart.Name }}-{{ .Chart.Version }}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ $.Release.Service }}" + "app.kubernetes.io/instance": "{{ $.Release.Name }}" + "app.kubernetes.io/component": "{{ $.Values.controller.componentName }}" +data: + auto-reload-config.yaml: |- + version: 1 + disable_existing_loggers: false + root: + level: {{ .Values.controller.sidecars.configAutoReload.logging.configuration.logLevel }} + handlers: + {{- if .Values.controller.sidecars.configAutoReload.logging.configuration.logToConsole}} + - console + {{- end }} + {{- if .Values.controller.sidecars.configAutoReload.logging.configuration.logToFile }} + - file + {{- end }} + handlers: + {{- if .Values.controller.sidecars.configAutoReload.logging.configuration.logToConsole}} + console: + class: logging.StreamHandler + level: {{ .Values.controller.sidecars.configAutoReload.logging.configuration.logLevel }} + formatter: {{ .Values.controller.sidecars.configAutoReload.logging.configuration.formatter }} + {{- end }} + {{- if .Values.controller.sidecars.configAutoReload.logging.configuration.logToFile }} + file: + class : logging.handlers.RotatingFileHandler + formatter: {{ .Values.controller.sidecars.configAutoReload.logging.configuration.formatter }} + filename: {{ .Values.controller.jenkinsHome }}/auto-reload-logs/file.log + maxBytes: {{ .Values.controller.sidecars.configAutoReload.logging.configuration.maxBytes }} + backupCount: {{ .Values.controller.sidecars.configAutoReload.logging.configuration.backupCount }} + {{- end }} + formatters: + JSON: + "()": logger.JsonFormatter + format: "%(levelname)s %(message)s" + rename_fields: + message: msg + levelname: level + LOGFMT: + "()": logger.LogfmtFormatter + keys: + - time + - level + - msg + mapping: + time: asctime + level: levelname + msg: message + {{- end }} \ No newline at end of file diff --git a/charts/jenkins/jenkins/5.5.0/templates/config-init-scripts.yaml b/charts/jenkins/jenkins/5.5.0/templates/config-init-scripts.yaml new file mode 100644 index 000000000..7dd253cc3 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/config-init-scripts.yaml @@ -0,0 +1,18 @@ +{{- if .Values.controller.initScripts -}} + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "jenkins.fullname" . }}-init-scripts + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +data: +{{- range $key, $val := .Values.controller.initScripts }} + init{{ $key }}.groovy: |- +{{ tpl $val $ | indent 4 }} +{{- end }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/config.yaml b/charts/jenkins/jenkins/5.5.0/templates/config.yaml new file mode 100644 index 000000000..5de0b9f72 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/config.yaml @@ -0,0 +1,92 @@ +{{- $jenkinsHome := .Values.controller.jenkinsHome -}} + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "jenkins.fullname" . }} + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +data: + apply_config.sh: |- + set -e +{{- if .Values.controller.initializeOnce }} + if [ -f {{ .Values.controller.jenkinsHome }}/initialization-completed ]; then + echo "controller was previously initialized, refusing to re-initialize" + exit 0 + fi +{{- end }} + echo "disable Setup Wizard" + # Prevent Setup Wizard when JCasC is enabled + echo $JENKINS_VERSION > {{ .Values.controller.jenkinsHome }}/jenkins.install.UpgradeWizard.state + echo $JENKINS_VERSION > {{ .Values.controller.jenkinsHome }}/jenkins.install.InstallUtil.lastExecVersion +{{- if .Values.controller.overwritePlugins }} + echo "remove all plugins from shared volume" + # remove all plugins from shared volume + rm -rf {{ .Values.controller.jenkinsHome }}/plugins/* +{{- end }} +{{- if .Values.controller.JCasC.overwriteConfiguration }} + echo "deleting all XML config files" + rm -f {{ .Values.controller.jenkinsHome }}/config.xml + rm -f {{ .Values.controller.jenkinsHome }}/*plugins*.xml + find {{ .Values.controller.jenkinsHome }} -maxdepth 1 -type f -iname '*configuration*.xml' -exec rm -f {} \; +{{- end }} +{{- if .Values.controller.installPlugins }} + echo "download plugins" + # Install missing plugins + cp /var/jenkins_config/plugins.txt {{ .Values.controller.jenkinsHome }}; + rm -rf {{ .Values.controller.jenkinsRef }}/plugins/*.lock + version () { echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; } + if [ -f "{{ .Values.controller.jenkinsWar }}" ] && [ -n "$(command -v jenkins-plugin-cli)" 2>/dev/null ] && [ $(version $(jenkins-plugin-cli --version)) -ge $(version "2.1.1") ]; then + jenkins-plugin-cli --verbose --war "{{ .Values.controller.jenkinsWar }}" --plugin-file "{{ .Values.controller.jenkinsHome }}/plugins.txt" --latest {{ .Values.controller.installLatestPlugins }}{{- if .Values.controller.installLatestSpecifiedPlugins }} --latest-specified{{- end }}; + else + /usr/local/bin/install-plugins.sh `echo $(cat {{ .Values.controller.jenkinsHome }}/plugins.txt)`; + fi + echo "copy plugins to shared volume" + # Copy plugins to shared volume + yes n | cp -i {{ .Values.controller.jenkinsRef }}/plugins/* /var/jenkins_plugins/; +{{- end }} + {{- if not .Values.controller.sidecars.configAutoReload.enabled }} + echo "copy configuration as code files" + mkdir -p {{ .Values.controller.jenkinsHome }}/casc_configs; + rm -rf {{ .Values.controller.jenkinsHome }}/casc_configs/* + {{- if or .Values.controller.JCasC.defaultConfig .Values.controller.JCasC.configScripts }} + cp -v /var/jenkins_config/*.yaml {{ .Values.controller.jenkinsHome }}/casc_configs + {{- end }} + {{- end }} + echo "finished initialization" +{{- if .Values.controller.initializeOnce }} + touch {{ .Values.controller.jenkinsHome }}/initialization-completed +{{- end }} + {{- if not .Values.controller.sidecars.configAutoReload.enabled }} +# Only add config to this script if we aren't auto-reloading otherwise the pod will restart upon each config change: +{{- if .Values.controller.JCasC.defaultConfig }} + jcasc-default-config.yaml: |- + {{- include "jenkins.casc.defaults" . |nindent 4}} +{{- end }} +{{- range $key, $val := .Values.controller.JCasC.configScripts }} + {{ $key }}.yaml: |- +{{ tpl $val $| indent 4 }} +{{- end }} +{{- end }} + plugins.txt: |- +{{- if .Values.controller.installPlugins }} + {{- range $installPlugin := .Values.controller.installPlugins }} + {{- $installPlugin | nindent 4 }} + {{- end }} + {{- range $addlPlugin := .Values.controller.additionalPlugins }} + {{- /* duplicate plugin check */}} + {{- range $installPlugin := $.Values.controller.installPlugins }} + {{- if eq (splitList ":" $addlPlugin | first) (splitList ":" $installPlugin | first) }} + {{- $message := print "[PLUGIN CONFLICT] controller.additionalPlugins contains '" $addlPlugin "'" }} + {{- $message := print $message " but controller.installPlugins already contains '" $installPlugin "'." }} + {{- $message := print $message " Override controller.installPlugins to use '" $addlPlugin "' plugin." }} + {{- fail $message }} + {{- end }} + {{- end }} + {{- $addlPlugin | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/deprecation.yaml b/charts/jenkins/jenkins/5.5.0/templates/deprecation.yaml new file mode 100644 index 000000000..f54017ce4 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/deprecation.yaml @@ -0,0 +1,151 @@ +{{- if .Values.checkDeprecation }} + {{- if .Values.master }} + {{ fail "`master` does no longer exist. It has been renamed to `controller`" }} + {{- end }} + + {{- if .Values.controller.imageTag }} + {{ fail "`controller.imageTag` does no longer exist. Please use `controller.image.tag` instead" }} + {{- end }} + + {{- if .Values.controller.slaveListenerPort }} + {{ fail "`controller.slaveListenerPort` does no longer exist. It has been renamed to `controller.agentListenerPort`" }} + {{- end }} + + {{- if .Values.controller.slaveHostPort }} + {{ fail "`controller.slaveHostPort` does no longer exist. It has been renamed to `controller.agentListenerHostPort`" }} + {{- end }} + + {{- if .Values.controller.slaveKubernetesNamespace }} + {{ fail "`controller.slaveKubernetesNamespace` does no longer exist. It has been renamed to `agent.namespace`" }} + {{- end }} + + {{- if .Values.controller.slaveDefaultsProviderTemplate }} + {{ fail "`controller.slaveDefaultsProviderTemplate` does no longer exist. It has been renamed to `agent.defaultsProviderTemplate`" }} + {{- end }} + + {{- if .Values.controller.useSecurity }} + {{ fail "`controller.useSecurity` does no longer exist. It has been renamed to `controller.adminSecret`" }} + {{- end }} + + {{- if .Values.controller.slaveJenkinsUrl }} + {{ fail "`controller.slaveJenkinsUrl` does no longer exist. It has been renamed to `agent.jenkinsUrl`" }} + {{- end }} + + {{- if .Values.controller.slaveJenkinsTunnel }} + {{ fail "`controller.slaveJenkinsTunnel` does no longer exist. It has been renamed to `agent.jenkinsTunnel`" }} + {{- end }} + + {{- if .Values.controller.slaveConnectTimeout }} + {{ fail "`controller.slaveConnectTimeout` does no longer exist. It has been renamed to `agent.kubernetesConnectTimeout`" }} + {{- end }} + + {{- if .Values.controller.slaveReadTimeout }} + {{ fail "`controller.slaveReadTimeout` does no longer exist. It has been renamed to `agent.kubernetesReadTimeout`" }} + {{- end }} + + {{- if .Values.controller.slaveListenerServiceType }} + {{ fail "`controller.slaveListenerServiceType` does no longer exist. It has been renamed to `controller.agentListenerServiceType`" }} + {{- end }} + + {{- if .Values.controller.slaveListenerLoadBalancerIP }} + {{ fail "`controller.slaveListenerLoadBalancerIP` does no longer exist. It has been renamed to `controller.agentListenerLoadBalancerIP`" }} + {{- end }} + + {{- if .Values.controller.slaveListenerServiceAnnotations }} + {{ fail "`controller.slaveListenerServiceAnnotations` does no longer exist. It has been renamed to `controller.agentListenerServiceAnnotations`" }} + {{- end }} + + {{- if .Values.agent.slaveConnectTimeout }} + {{ fail "`agent.slaveConnectTimeout` does no longer exist. It has been renamed to `agent.connectTimeout`" }} + {{- end }} + + {{- if .Values.NetworkPolicy }} + + {{- if .Values.NetworkPolicy.Enabled }} + {{ fail "`NetworkPolicy.Enabled` does no longer exist. It has been renamed to `networkPolicy.enabled`" }} + {{- end }} + + {{- if .Values.NetworkPolicy.ApiVersion }} + {{ fail "`NetworkPolicy.ApiVersion` does no longer exist. It has been renamed to `networkPolicy.apiVersion`" }} + {{- end }} + + {{ fail "NetworkPolicy.* values have been renamed, please check the documentation" }} + {{- end }} + + + {{- if .Values.rbac.install }} + {{ fail "`rbac.install` does no longer exist. It has been renamed to `rbac.create` and is enabled by default!" }} + {{- end }} + + {{- if .Values.rbac.serviceAccountName }} + {{ fail "`rbac.serviceAccountName` does no longer exist. It has been renamed to `serviceAccount.name`" }} + {{- end }} + + {{- if .Values.rbac.serviceAccountAnnotations }} + {{ fail "`rbac.serviceAccountAnnotations` does no longer exist. It has been renamed to `serviceAccount.annotations`" }} + {{- end }} + + {{- if .Values.rbac.roleRef }} + {{ fail "`rbac.roleRef` does no longer exist. RBAC roles are now generated, please check the documentation" }} + {{- end }} + + {{- if .Values.rbac.roleKind }} + {{ fail "`rbac.roleKind` does no longer exist. RBAC roles are now generated, please check the documentation" }} + {{- end }} + + {{- if .Values.rbac.roleBindingKind }} + {{ fail "`rbac.roleBindingKind` does no longer exist. RBAC roles are now generated, please check the documentation" }} + {{- end }} + + {{- if .Values.controller.JCasC.pluginVersion }} + {{ fail "controller.JCasC.pluginVersion has been deprecated, please use controller.installPlugins instead" }} + {{- end }} + + {{- if .Values.controller.deploymentLabels }} + {{ fail "`controller.deploymentLabels` does no longer exist. It has been renamed to `controller.statefulSetLabels`" }} + {{- end }} + + {{- if .Values.controller.deploymentAnnotations }} + {{ fail "`controller.deploymentAnnotations` does no longer exist. It has been renamed to `controller.statefulSetAnnotations`" }} + {{- end }} + + {{- if .Values.controller.rollingUpdate }} + {{ fail "`controller.rollingUpdate` does no longer exist. It is no longer relevant, since a StatefulSet is used for the Jenkins controller" }} + {{- end }} + + {{- if .Values.controller.tag }} + {{ fail "`controller.tag` no longer exists. It has been renamed to `controller.image.tag'" }} + {{- end }} + + {{- if .Values.controller.tagLabel }} + {{ fail "`controller.tagLabel` no longer exists. It has been renamed to `controller.image.tagLabel`" }} + {{- end }} + + {{- if .Values.controller.adminSecret }} + {{ fail "`controller.adminSecret` no longer exists. It has been renamed to `controller.admin.createSecret`" }} + {{- end }} + + {{- if .Values.controller.adminUser }} + {{ fail "`controller.adminUser` no longer exists. It has been renamed to `controller.admin.username`" }} + {{- end }} + + {{- if .Values.controller.adminPassword }} + {{ fail "`controller.adminPassword` no longer exists. It has been renamed to `controller.admin.password`" }} + {{- end }} + + {{- if .Values.controller.sidecars.other }} + {{ fail "`controller.sidecars.other` no longer exists. It has been renamed to `controller.sidecars.additionalSidecarContainers`" }} + {{- end }} + + {{- if .Values.agent.tag }} + {{ fail "`controller.agent.tag` no longer exists. It has been renamed to `controller.agent.image.tag`" }} + {{- end }} + + {{- if .Values.backup }} + {{ fail "`controller.backup` no longer exists." }} + {{- end }} + + {{- if .Values.helmtest.bats.tag }} + {{ fail "`helmtest.bats.tag` no longer exists. It has been renamed to `helmtest.bats.image.tag`" }} + {{- end }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/home-pvc.yaml b/charts/jenkins/jenkins/5.5.0/templates/home-pvc.yaml new file mode 100644 index 000000000..f417d23ad --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/home-pvc.yaml @@ -0,0 +1,41 @@ +{{- if not (contains "jenkins-home" (quote .Values.persistence.volumes)) }} +{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) -}} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: +{{- if .Values.persistence.annotations }} + annotations: +{{ toYaml .Values.persistence.annotations | indent 4 }} +{{- end }} + name: {{ template "jenkins.fullname" . }} + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +{{- if .Values.persistence.labels }} +{{ toYaml .Values.persistence.labels | indent 4 }} +{{- end }} +spec: +{{- if .Values.persistence.dataSource }} + dataSource: +{{ toYaml .Values.persistence.dataSource | indent 4 }} +{{- end }} + accessModes: + - {{ .Values.persistence.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} +{{- if .Values.persistence.storageClass }} +{{- if (eq "-" .Values.persistence.storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: "{{ .Values.persistence.storageClass }}" +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/jcasc-config.yaml b/charts/jenkins/jenkins/5.5.0/templates/jcasc-config.yaml new file mode 100644 index 000000000..f51444525 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/jcasc-config.yaml @@ -0,0 +1,53 @@ +{{- $root := . }} +{{- if .Values.controller.sidecars.configAutoReload.enabled }} +{{- range $key, $val := .Values.controller.JCasC.configScripts }} +{{- if $val }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "jenkins.casc.configName" (list (printf "config-%s" $key) $ )}} + namespace: {{ template "jenkins.namespace" $root }} + labels: + "app.kubernetes.io/name": {{ template "jenkins.name" $root}} + {{- if $root.Values.renderHelmLabels }} + "helm.sh/chart": "{{ $root.Chart.Name }}-{{ $root.Chart.Version }}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ $.Release.Service }}" + "app.kubernetes.io/instance": "{{ $.Release.Name }}" + "app.kubernetes.io/component": "{{ $.Values.controller.componentName }}" + {{ template "jenkins.fullname" $root }}-jenkins-config: "true" +{{- if $root.Values.controller.JCasC.configMapAnnotations }} + annotations: +{{ toYaml $root.Values.controller.JCasC.configMapAnnotations | indent 4 }} +{{- end }} +data: + {{ $key }}.yaml: |- +{{ tpl $val $| indent 4 }} +{{- end }} +{{- end }} +{{- if .Values.controller.JCasC.defaultConfig }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "jenkins.casc.configName" (list "jcasc-config" $ )}} + namespace: {{ template "jenkins.namespace" $root }} + labels: + "app.kubernetes.io/name": {{ template "jenkins.name" $root}} + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ $root.Chart.Name }}-{{ $root.Chart.Version }}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ $.Release.Service }}" + "app.kubernetes.io/instance": "{{ $.Release.Name }}" + "app.kubernetes.io/component": "{{ $.Values.controller.componentName }}" + {{ template "jenkins.fullname" $root }}-jenkins-config: "true" +{{- if $root.Values.controller.JCasC.configMapAnnotations }} + annotations: +{{ toYaml $root.Values.controller.JCasC.configMapAnnotations | indent 4 }} +{{- end }} +data: + jcasc-default-config.yaml: |- + {{- include "jenkins.casc.defaults" . | nindent 4 }} +{{- end}} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/jenkins-agent-svc.yaml b/charts/jenkins/jenkins/5.5.0/templates/jenkins-agent-svc.yaml new file mode 100644 index 000000000..4440b91f8 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/jenkins-agent-svc.yaml @@ -0,0 +1,43 @@ +{{- if .Values.controller.agentListenerEnabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "jenkins.fullname" . }}-agent + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + {{- if .Values.controller.agentListenerServiceAnnotations }} + annotations: + {{- toYaml .Values.controller.agentListenerServiceAnnotations | nindent 4 }} + {{- end }} +spec: + {{- if .Values.controller.agentListenerExternalTrafficPolicy }} + externalTrafficPolicy: {{.Values.controller.agentListenerExternalTrafficPolicy}} + {{- end }} + ports: + - port: {{ .Values.controller.agentListenerPort }} + targetPort: {{ .Values.controller.agentListenerPort }} + {{- if (and (eq .Values.controller.agentListenerServiceType "NodePort") (not (empty .Values.controller.agentListenerNodePort))) }} + nodePort: {{ .Values.controller.agentListenerNodePort }} + {{- end }} + name: agent-listener + selector: + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + type: {{ .Values.controller.agentListenerServiceType }} + {{if eq .Values.controller.agentListenerServiceType "LoadBalancer"}} +{{- if .Values.controller.agentListenerLoadBalancerSourceRanges }} + loadBalancerSourceRanges: +{{ toYaml .Values.controller.agentListenerLoadBalancerSourceRanges | indent 4 }} +{{- end }} + {{- end }} + {{- if and (eq .Values.controller.agentListenerServiceType "LoadBalancer") (.Values.controller.agentListenerLoadBalancerIP) }} + loadBalancerIP: {{ .Values.controller.agentListenerLoadBalancerIP }} + {{- end }} + {{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/jenkins-aws-security-group-policies.yaml b/charts/jenkins/jenkins/5.5.0/templates/jenkins-aws-security-group-policies.yaml new file mode 100644 index 000000000..2f6e7a13d --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/jenkins-aws-security-group-policies.yaml @@ -0,0 +1,16 @@ +{{- if .Values.awsSecurityGroupPolicies.enabled -}} +{{- range .Values.awsSecurityGroupPolicies.policies -}} +apiVersion: vpcresources.k8s.aws/v1beta1 +kind: SecurityGroupPolicy +metadata: + name: {{ .name }} + namespace: {{ template "jenkins.namespace" $ }} +spec: + podSelector: + {{- toYaml .podSelector | nindent 6}} + securityGroups: + groupIds: + {{- toYaml .securityGroupIds | nindent 6}} +--- +{{- end -}} +{{- end -}} diff --git a/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-alerting-rules.yaml b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-alerting-rules.yaml new file mode 100644 index 000000000..3fd806172 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-alerting-rules.yaml @@ -0,0 +1,26 @@ +{{- if and .Values.controller.prometheus.enabled .Values.controller.prometheus.alertingrules }} +--- +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ template "jenkins.fullname" . }} +{{- if .Values.controller.prometheus.prometheusRuleNamespace }} + namespace: {{ .Values.controller.prometheus.prometheusRuleNamespace }} +{{- else }} + namespace: {{ template "jenkins.namespace" . }} +{{- end }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + {{- range $key, $val := .Values.controller.prometheus.alertingRulesAdditionalLabels }} + {{ $key }}: {{ $val | quote }} + {{- end}} +spec: + groups: +{{ toYaml .Values.controller.prometheus.alertingrules | indent 2 }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-backendconfig.yaml b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-backendconfig.yaml new file mode 100644 index 000000000..0e8a566fc --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-backendconfig.yaml @@ -0,0 +1,24 @@ +{{- if .Values.controller.backendconfig.enabled }} +apiVersion: {{ .Values.controller.backendconfig.apiVersion }} +kind: BackendConfig +metadata: + name: {{ .Values.controller.backendconfig.name }} + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +{{- if .Values.controller.backendconfig.labels }} +{{ toYaml .Values.controller.backendconfig.labels | indent 4 }} +{{- end }} +{{- if .Values.controller.backendconfig.annotations }} + annotations: +{{ toYaml .Values.controller.backendconfig.annotations | indent 4 }} +{{- end }} +spec: +{{ toYaml .Values.controller.backendconfig.spec | indent 2 }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-ingress.yaml b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-ingress.yaml new file mode 100644 index 000000000..b3b344ff8 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-ingress.yaml @@ -0,0 +1,77 @@ +{{- $kubeTargetVersion := default .Capabilities.KubeVersion.GitVersion .Values.kubeTargetVersionOverride }} +{{- if .Values.controller.ingress.enabled }} +{{- if semverCompare ">=1.19-0" $kubeTargetVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" $kubeTargetVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: {{ .Values.controller.ingress.apiVersion }} +{{- end }} +kind: Ingress +metadata: + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +{{- if .Values.controller.ingress.labels }} +{{ toYaml .Values.controller.ingress.labels | indent 4 }} +{{- end }} +{{- if .Values.controller.ingress.annotations }} + annotations: +{{ toYaml .Values.controller.ingress.annotations | indent 4 }} +{{- end }} + name: {{ template "jenkins.fullname" . }} +spec: +{{- if .Values.controller.ingress.ingressClassName }} + ingressClassName: {{ .Values.controller.ingress.ingressClassName | quote }} +{{- end }} + rules: + - http: + paths: +{{- if empty (.Values.controller.ingress.paths) }} + - backend: +{{- if semverCompare ">=1.19-0" $kubeTargetVersion }} + service: + name: {{ template "jenkins.fullname" . }} + port: + number: {{ .Values.controller.servicePort }} + pathType: ImplementationSpecific +{{- else }} + serviceName: {{ template "jenkins.fullname" . }} + servicePort: {{ .Values.controller.servicePort }} +{{- end }} +{{- if .Values.controller.ingress.path }} + path: {{ .Values.controller.ingress.path }} +{{- end -}} +{{- else }} +{{ tpl (toYaml .Values.controller.ingress.paths | indent 6) . }} +{{- end -}} +{{- if .Values.controller.ingress.hostName }} + host: {{ tpl .Values.controller.ingress.hostName . | quote }} +{{- end }} +{{- if .Values.controller.ingress.resourceRootUrl }} + - http: + paths: + - backend: +{{- if semverCompare ">=1.19-0" $kubeTargetVersion }} + service: + name: {{ template "jenkins.fullname" . }} + port: + number: {{ .Values.controller.servicePort }} + pathType: ImplementationSpecific +{{- else }} + serviceName: {{ template "jenkins.fullname" . }} + servicePort: {{ .Values.controller.servicePort }} +{{- end }} + host: {{ tpl .Values.controller.ingress.resourceRootUrl . | quote }} +{{- end }} +{{- if .Values.controller.ingress.tls }} + tls: +{{ tpl (toYaml .Values.controller.ingress.tls ) . | indent 4 }} +{{- end -}} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-networkpolicy.yaml b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-networkpolicy.yaml new file mode 100644 index 000000000..82835f2bd --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-networkpolicy.yaml @@ -0,0 +1,76 @@ +{{- if .Values.networkPolicy.enabled }} +kind: NetworkPolicy +apiVersion: {{ .Values.networkPolicy.apiVersion }} +metadata: + name: "{{ .Release.Name }}-{{ .Values.controller.componentName }}" + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +spec: + podSelector: + matchLabels: + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + ingress: + # Allow web access to the UI + - ports: + - port: {{ .Values.controller.targetPort }} + {{- if .Values.controller.agentListenerEnabled }} + # Allow inbound connections from agents + - from: + {{- if .Values.networkPolicy.internalAgents.allowed }} + - podSelector: + matchLabels: + "jenkins/{{ .Release.Name }}-{{ .Values.agent.componentName }}": "true" + {{- range $k,$v:= .Values.networkPolicy.internalAgents.podLabels }} + {{ $k }}: {{ $v }} + {{- end }} + {{- if .Values.networkPolicy.internalAgents.namespaceLabels }} + namespaceSelector: + matchLabels: + {{- range $k,$v:= .Values.networkPolicy.internalAgents.namespaceLabels }} + {{ $k }}: {{ $v }} + {{- end }} + {{- end }} + {{- end }} + {{- if or .Values.networkPolicy.externalAgents.ipCIDR .Values.networkPolicy.externalAgents.except }} + - ipBlock: + cidr: {{ required "ipCIDR is required if you wish to allow external agents to connect to Jenkins Controller." .Values.networkPolicy.externalAgents.ipCIDR }} + {{- if .Values.networkPolicy.externalAgents.except }} + except: + {{- range .Values.networkPolicy.externalAgents.except }} + - {{ . }} + {{- end }} + {{- end }} + {{- end }} + ports: + - port: {{ .Values.controller.agentListenerPort }} + {{- end }} +{{- if .Values.agent.enabled }} +--- +kind: NetworkPolicy +apiVersion: {{ .Values.networkPolicy.apiVersion }} +metadata: + name: "{{ .Release.Name }}-{{ .Values.agent.componentName }}" + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +spec: + podSelector: + matchLabels: + # DefaultDeny + "jenkins/{{ .Release.Name }}-{{ .Values.agent.componentName }}": "true" +{{- end }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-pdb.yaml b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-pdb.yaml new file mode 100644 index 000000000..9dc1fafe2 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-pdb.yaml @@ -0,0 +1,34 @@ +{{- if .Values.controller.podDisruptionBudget.enabled }} +{{- $kubeTargetVersion := default .Capabilities.KubeVersion.GitVersion .Values.kubeTargetVersionOverride }} +{{- if semverCompare ">=1.21-0" $kubeTargetVersion -}} +apiVersion: policy/v1 +{{- else if semverCompare ">=1.5-0" $kubeTargetVersion -}} +apiVersion: policy/v1beta1 +{{- else -}} +apiVersion: {{ .Values.controller.podDisruptionBudget.apiVersion }} +{{- end }} +kind: PodDisruptionBudget +metadata: + name: {{ template "jenkins.fullname" . }}-pdb + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + {{- if .Values.controller.podDisruptionBudget.labels -}} + {{ toYaml .Values.controller.podDisruptionBudget.labels | nindent 4 }} + {{- end }} + {{- if .Values.controller.podDisruptionBudget.annotations }} + annotations: {{ toYaml .Values.controller.podDisruptionBudget.annotations | nindent 4 }} + {{- end }} +spec: + maxUnavailable: {{ .Values.controller.podDisruptionBudget.maxUnavailable }} + selector: + matchLabels: + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-podmonitor.yaml b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-podmonitor.yaml new file mode 100644 index 000000000..9a04019c3 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-podmonitor.yaml @@ -0,0 +1,30 @@ +{{- if .Values.controller.googlePodMonitor.enabled }} +apiVersion: monitoring.googleapis.com/v1 +kind: PodMonitoring + +metadata: + name: {{ template "jenkins.fullname" . }} +{{- if .Values.controller.googlePodMonitor.serviceMonitorNamespace }} + namespace: {{ .Values.controller.googlePodMonitor.serviceMonitorNamespace }} +{{- else }} + namespace: {{ template "jenkins.namespace" . }} +{{- end }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + +spec: + endpoints: + - interval: {{ .Values.controller.googlePodMonitor.scrapeInterval }} + port: http + path: {{ .Values.controller.jenkinsUriPrefix }}{{ .Values.controller.googlePodMonitor.scrapeEndpoint }} + selector: + matchLabels: + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-route.yaml b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-route.yaml new file mode 100644 index 000000000..3550380ee --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-route.yaml @@ -0,0 +1,34 @@ +{{- if .Values.controller.route.enabled }} +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + namespace: {{ template "jenkins.namespace" . }} + labels: + app: {{ template "jenkins.fullname" . }} + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + component: "{{ .Release.Name }}-{{ .Values.controller.componentName }}" +{{- if .Values.controller.route.labels }} +{{ toYaml .Values.controller.route.labels | indent 4 }} +{{- end }} +{{- if .Values.controller.route.annotations }} + annotations: +{{ toYaml .Values.controller.route.annotations | indent 4 }} +{{- end }} + name: {{ template "jenkins.fullname" . }} +spec: + host: {{ .Values.controller.route.path }} + port: + targetPort: http + tls: + insecureEdgeTerminationPolicy: Redirect + termination: edge + to: + kind: Service + name: {{ template "jenkins.fullname" . }} + weight: 100 + wildcardPolicy: None +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-secondary-ingress.yaml b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-secondary-ingress.yaml new file mode 100644 index 000000000..c63e48229 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-secondary-ingress.yaml @@ -0,0 +1,56 @@ +{{- if .Values.controller.secondaryingress.enabled }} +{{- $kubeTargetVersion := default .Capabilities.KubeVersion.GitVersion .Values.kubeTargetVersionOverride }} +{{- $serviceName := include "jenkins.fullname" . -}} +{{- $servicePort := .Values.controller.servicePort -}} +{{- if semverCompare ">=1.19-0" $kubeTargetVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" $kubeTargetVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: {{ .Values.controller.secondaryingress.apiVersion }} +{{- end }} +kind: Ingress +metadata: + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + {{- if .Values.controller.secondaryingress.labels -}} + {{ toYaml .Values.controller.secondaryingress.labels | nindent 4 }} + {{- end }} + {{- if .Values.controller.secondaryingress.annotations }} + annotations: {{ toYaml .Values.controller.secondaryingress.annotations | nindent 4 }} + {{- end }} + name: {{ template "jenkins.fullname" . }}-secondary +spec: +{{- if .Values.controller.secondaryingress.ingressClassName }} + ingressClassName: {{ .Values.controller.secondaryingress.ingressClassName | quote }} +{{- end }} + rules: + - host: {{ .Values.controller.secondaryingress.hostName }} + http: + paths: + {{- range .Values.controller.secondaryingress.paths }} + - path: {{ . | quote }} + backend: +{{ if semverCompare ">=1.19-0" $kubeTargetVersion }} + service: + name: {{ $serviceName }} + port: + number: {{ $servicePort }} + pathType: ImplementationSpecific +{{ else }} + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} +{{ end }} + {{- end}} +{{- if .Values.controller.secondaryingress.tls }} + tls: +{{ toYaml .Values.controller.secondaryingress.tls | indent 4 }} +{{- end -}} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-servicemonitor.yaml b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-servicemonitor.yaml new file mode 100644 index 000000000..8710b2bc9 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-servicemonitor.yaml @@ -0,0 +1,45 @@ +{{- if and .Values.controller.prometheus.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor + +metadata: + name: {{ template "jenkins.fullname" . }} +{{- if .Values.controller.prometheus.serviceMonitorNamespace }} + namespace: {{ .Values.controller.prometheus.serviceMonitorNamespace }} +{{- else }} + namespace: {{ template "jenkins.namespace" . }} +{{- end }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + {{- range $key, $val := .Values.controller.prometheus.serviceMonitorAdditionalLabels }} + {{ $key }}: {{ $val | quote }} + {{- end}} + +spec: + endpoints: + - interval: {{ .Values.controller.prometheus.scrapeInterval }} + port: http + path: {{ .Values.controller.jenkinsUriPrefix }}{{ .Values.controller.prometheus.scrapeEndpoint }} + {{- with .Values.controller.prometheus.relabelings }} + relabelings: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.controller.prometheus.metricRelabelings }} + metricRelabelings: + {{- toYaml . | nindent 6 }} + {{- end }} + jobLabel: {{ template "jenkins.fullname" . }} + namespaceSelector: + matchNames: + - "{{ template "jenkins.namespace" $ }}" + selector: + matchLabels: + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-statefulset.yaml b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-statefulset.yaml new file mode 100644 index 000000000..50e61acf1 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-statefulset.yaml @@ -0,0 +1,424 @@ +{{- if .Capabilities.APIVersions.Has "apps/v1" }} +apiVersion: apps/v1 +{{- else }} +apiVersion: apps/v1beta1 +{{- end }} +kind: StatefulSet +metadata: + name: {{ template "jenkins.fullname" . }} + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + {{- range $key, $val := .Values.controller.statefulSetLabels }} + {{ $key }}: {{ $val | quote }} + {{- end}} + {{- if .Values.controller.statefulSetAnnotations }} + annotations: +{{ toYaml .Values.controller.statefulSetAnnotations | indent 4 }} + {{- end }} +spec: + serviceName: {{ template "jenkins.fullname" . }} + replicas: 1 + selector: + matchLabels: + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + {{- if .Values.controller.updateStrategy }} + updateStrategy: +{{ toYaml .Values.controller.updateStrategy | indent 4 }} + {{- end }} + template: + metadata: + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + {{- range $key, $val := .Values.controller.podLabels }} + {{ $key }}: {{ $val | quote }} + {{- end}} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/config.yaml") . | sha256sum }} + {{- if .Values.controller.initScripts }} + checksum/config-init-scripts: {{ include (print $.Template.BasePath "/config-init-scripts.yaml") . | sha256sum }} + {{- end }} + {{- if .Values.controller.podAnnotations }} +{{ tpl (toYaml .Values.controller.podAnnotations | indent 8) . }} + {{- end }} + spec: + {{- if .Values.controller.schedulerName }} + schedulerName: {{ .Values.controller.schedulerName }} + {{- end }} + {{- if .Values.controller.nodeSelector }} + nodeSelector: +{{ toYaml .Values.controller.nodeSelector | indent 8 }} + {{- end }} + {{- if .Values.controller.tolerations }} + tolerations: +{{ toYaml .Values.controller.tolerations | indent 8 }} + {{- end }} + {{- if .Values.controller.affinity }} + affinity: +{{ toYaml .Values.controller.affinity | indent 8 }} + {{- end }} + {{- if .Values.controller.topologySpreadConstraints }} + topologySpreadConstraints: +{{ toYaml .Values.controller.topologySpreadConstraints | indent 8 }} + {{- end }} + {{- if quote .Values.controller.terminationGracePeriodSeconds }} + terminationGracePeriodSeconds: {{ .Values.controller.terminationGracePeriodSeconds }} + {{- end }} + {{- if .Values.controller.priorityClassName }} + priorityClassName: {{ .Values.controller.priorityClassName }} + {{- end }} + {{- if .Values.controller.shareProcessNamespace }} + shareProcessNamespace: true + {{- end }} +{{- if .Values.controller.usePodSecurityContext }} + securityContext: + {{- if kindIs "map" .Values.controller.podSecurityContextOverride }} + {{- tpl (toYaml .Values.controller.podSecurityContextOverride | nindent 8) . -}} + {{- else }} + {{/* The rest of this section should be replaced with the contents of this comment one the runAsUser, fsGroup, and securityContextCapabilities Helm chart values have been removed: + runAsUser: 1000 + fsGroup: 1000 + runAsNonRoot: true + */}} + runAsUser: {{ default 0 .Values.controller.runAsUser }} + {{- if and (.Values.controller.runAsUser) (.Values.controller.fsGroup) }} + {{- if not (eq (int .Values.controller.runAsUser) 0) }} + fsGroup: {{ .Values.controller.fsGroup }} + runAsNonRoot: true + {{- end }} + {{- if .Values.controller.securityContextCapabilities }} + capabilities: + {{- toYaml .Values.controller.securityContextCapabilities | nindent 10 }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} + serviceAccountName: "{{ template "jenkins.serviceAccountName" . }}" +{{- if .Values.controller.hostNetworking }} + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet +{{- end }} + {{- if .Values.controller.hostAliases }} + hostAliases: + {{- toYaml .Values.controller.hostAliases | nindent 8 }} + {{- end }} + initContainers: +{{- if .Values.controller.customInitContainers }} +{{ tpl (toYaml .Values.controller.customInitContainers) . | indent 8 }} +{{- end }} + +{{- if .Values.controller.sidecars.configAutoReload.enabled }} +{{- include "jenkins.configReloadContainer" (list $ "config-reload-init" "init") | nindent 8 }} +{{- end}} + + - name: "init" + image: "{{ .Values.controller.image.registry }}/{{ .Values.controller.image.repository }}:{{- include "controller.image.tag" . -}}" + imagePullPolicy: "{{ .Values.controller.image.pullPolicy }}" + {{- if .Values.controller.containerSecurityContext }} + securityContext: {{- toYaml .Values.controller.containerSecurityContext | nindent 12 }} + {{- end }} + command: [ "sh", "/var/jenkins_config/apply_config.sh" ] + {{- if .Values.controller.initContainerEnvFrom }} + envFrom: +{{ (tpl (toYaml .Values.controller.initContainerEnvFrom) .) | indent 12 }} + {{- end }} + {{- if .Values.controller.initContainerEnv }} + env: +{{ (tpl (toYaml .Values.controller.initContainerEnv) .) | indent 12 }} + {{- end }} + resources: +{{- if .Values.controller.initContainerResources }} +{{ toYaml .Values.controller.initContainerResources | indent 12 }} +{{- else }} +{{ toYaml .Values.controller.resources | indent 12 }} +{{- end }} + volumeMounts: + {{- if .Values.persistence.mounts }} +{{ toYaml .Values.persistence.mounts | indent 12 }} + {{- end }} + - mountPath: {{ .Values.controller.jenkinsHome }} + name: jenkins-home + {{- if .Values.persistence.subPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + - mountPath: /var/jenkins_config + name: jenkins-config + {{- if .Values.controller.installPlugins }} + {{- if .Values.controller.overwritePluginsFromImage }} + - mountPath: {{ .Values.controller.jenkinsRef }}/plugins + name: plugins + {{- end }} + - mountPath: /var/jenkins_plugins + name: plugin-dir + - mountPath: /tmp + name: tmp-volume + {{- end }} + {{- if or .Values.controller.initScripts .Values.controller.initConfigMap }} + - mountPath: {{ .Values.controller.jenkinsHome }}/init.groovy.d + name: init-scripts + {{- end }} + {{- if and .Values.controller.httpsKeyStore.enable (not .Values.controller.httpsKeyStore.disableSecretMount) }} + {{- $httpsJKSDirPath := printf "%s" .Values.controller.httpsKeyStore.path }} + - mountPath: {{ $httpsJKSDirPath }} + name: jenkins-https-keystore + {{- end }} + containers: + - name: jenkins + image: "{{ .Values.controller.image.registry }}/{{ .Values.controller.image.repository }}:{{- include "controller.image.tag" . -}}" + imagePullPolicy: "{{ .Values.controller.image.pullPolicy }}" + {{- if .Values.controller.containerSecurityContext }} + securityContext: {{- toYaml .Values.controller.containerSecurityContext | nindent 12 }} + {{- end }} + {{- if .Values.controller.overrideArgs }} + args: [ + {{- range $overrideArg := .Values.controller.overrideArgs }} + "{{- tpl $overrideArg $ }}", + {{- end }} + ] + {{- else if .Values.controller.httpsKeyStore.enable }} + {{- $httpsJKSFilePath := printf "%s/%s" .Values.controller.httpsKeyStore.path .Values.controller.httpsKeyStore.fileName }} + args: [ "--httpPort={{.Values.controller.httpsKeyStore.httpPort}}", "--httpsPort={{.Values.controller.targetPort}}", '--httpsKeyStore={{ $httpsJKSFilePath }}', "--httpsKeyStorePassword=$(JENKINS_HTTPS_KEYSTORE_PASSWORD)" ] + {{- else }} + args: [ "--httpPort={{.Values.controller.targetPort}}"] + {{- end }} + {{- if .Values.controller.lifecycle }} + lifecycle: +{{ toYaml .Values.controller.lifecycle | indent 12 }} + {{- end }} +{{- if .Values.controller.terminationMessagePath }} + terminationMessagePath: {{ .Values.controller.terminationMessagePath }} +{{- end }} +{{- if .Values.controller.terminationMessagePolicy }} + terminationMessagePolicy: {{ .Values.controller.terminationMessagePolicy }} +{{- end }} + {{- if .Values.controller.containerEnvFrom }} + envFrom: +{{ (tpl ( toYaml .Values.controller.containerEnvFrom) .) | indent 12 }} + {{- end }} + env: + {{- if .Values.controller.containerEnv }} +{{ (tpl ( toYaml .Values.controller.containerEnv) .) | indent 12 }} + {{- end }} + {{- if or .Values.controller.additionalSecrets .Values.controller.existingSecret .Values.controller.additionalExistingSecrets .Values.controller.admin.createSecret }} + - name: SECRETS + value: /run/secrets/additional + {{- end }} + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: JAVA_OPTS + value: >- + {{ if .Values.controller.sidecars.configAutoReload.enabled }} -Dcasc.reload.token=$(POD_NAME) {{ end }}{{ default "" .Values.controller.javaOpts }} + - name: JENKINS_OPTS + value: >- + {{ if .Values.controller.jenkinsUriPrefix }}--prefix={{ .Values.controller.jenkinsUriPrefix }} {{ end }} --webroot=/var/jenkins_cache/war {{ default "" .Values.controller.jenkinsOpts}} + - name: JENKINS_SLAVE_AGENT_PORT + value: "{{ .Values.controller.agentListenerPort }}" + {{- if .Values.controller.httpsKeyStore.enable }} + - name: JENKINS_HTTPS_KEYSTORE_PASSWORD + {{- if not .Values.controller.httpsKeyStore.disableSecretMount }} + valueFrom: + secretKeyRef: + name: {{ if .Values.controller.httpsKeyStore.jenkinsHttpsJksPasswordSecretName }} {{ .Values.controller.httpsKeyStore.jenkinsHttpsJksPasswordSecretName }} {{ else if .Values.controller.httpsKeyStore.jenkinsHttpsJksSecretName }} {{ .Values.controller.httpsKeyStore.jenkinsHttpsJksSecretName }} {{ else }} {{ template "jenkins.fullname" . }}-https-jks {{ end }} + key: "{{ .Values.controller.httpsKeyStore.jenkinsHttpsJksPasswordSecretKey }}" + {{- else }} + value: {{ .Values.controller.httpsKeyStore.password }} + {{- end }} + {{- end }} + + - name: CASC_JENKINS_CONFIG + value: {{ .Values.controller.sidecars.configAutoReload.folder | default (printf "%s/casc_configs" (.Values.controller.jenkinsRef)) }}{{- if .Values.controller.JCasC.configUrls }},{{ join "," .Values.controller.JCasC.configUrls }}{{- end }} + ports: + {{- if .Values.controller.httpsKeyStore.enable }} + - containerPort: {{.Values.controller.httpsKeyStore.httpPort}} + {{- else }} + - containerPort: {{.Values.controller.targetPort}} + {{- end }} + name: http + - containerPort: {{ .Values.controller.agentListenerPort }} + name: agent-listener + {{- if .Values.controller.agentListenerHostPort }} + hostPort: {{ .Values.controller.agentListenerHostPort }} + {{- end }} + {{- if .Values.controller.jmxPort }} + - containerPort: {{ .Values.controller.jmxPort }} + name: jmx + {{- end }} +{{- range $index, $port := .Values.controller.extraPorts }} + - containerPort: {{ $port.port }} + name: {{ $port.name }} +{{- end }} +{{- if and .Values.controller.healthProbes .Values.controller.probes}} + {{- if semverCompare ">=1.16-0" .Capabilities.KubeVersion.GitVersion }} + startupProbe: +{{ tpl (toYaml .Values.controller.probes.startupProbe | indent 12) .}} + {{- end }} + livenessProbe: +{{ tpl (toYaml .Values.controller.probes.livenessProbe | indent 12) .}} + readinessProbe: +{{ tpl (toYaml .Values.controller.probes.readinessProbe | indent 12) .}} +{{- end }} + resources: +{{ toYaml .Values.controller.resources | indent 12 }} + volumeMounts: +{{- if .Values.persistence.mounts }} +{{ toYaml .Values.persistence.mounts | indent 12 }} +{{- end }} + {{- if and .Values.controller.httpsKeyStore.enable (not .Values.controller.httpsKeyStore.disableSecretMount) }} + {{- $httpsJKSDirPath := printf "%s" .Values.controller.httpsKeyStore.path }} + - mountPath: {{ $httpsJKSDirPath }} + name: jenkins-https-keystore + {{- end }} + - mountPath: {{ .Values.controller.jenkinsHome }} + name: jenkins-home + readOnly: false + {{- if .Values.persistence.subPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + - mountPath: /var/jenkins_config + name: jenkins-config + readOnly: true + {{- if .Values.controller.installPlugins }} + - mountPath: {{ .Values.controller.jenkinsRef }}/plugins/ + name: plugin-dir + readOnly: false + {{- end }} + {{- if or .Values.controller.initScripts .Values.controller.initConfigMap }} + - mountPath: {{ .Values.controller.jenkinsHome }}/init.groovy.d + name: init-scripts + {{- end }} + {{- if .Values.controller.sidecars.configAutoReload.enabled }} + - name: sc-config-volume + mountPath: {{ .Values.controller.sidecars.configAutoReload.folder | default (printf "%s/casc_configs" (.Values.controller.jenkinsRef)) }} + {{- end }} + {{- if or .Values.controller.additionalSecrets .Values.controller.existingSecret .Values.controller.additionalExistingSecrets .Values.controller.admin.createSecret }} + - name: jenkins-secrets + mountPath: /run/secrets/additional + readOnly: true + {{- end }} + - name: jenkins-cache + mountPath: /var/jenkins_cache + - mountPath: /tmp + name: tmp-volume + +{{- if .Values.controller.sidecars.configAutoReload.enabled }} +{{- include "jenkins.configReloadContainer" (list $ "config-reload" "sidecar") | nindent 8 }} +{{- end}} + + +{{- if .Values.controller.sidecars.additionalSidecarContainers}} +{{ tpl (toYaml .Values.controller.sidecars.additionalSidecarContainers | indent 8) .}} +{{- end }} + + volumes: +{{- if .Values.persistence.volumes }} +{{ tpl (toYaml .Values.persistence.volumes | indent 6) . }} +{{- end }} + {{- if .Values.controller.sidecars.configAutoReload.logging.configuration.override }} + - name: auto-reload-config + configMap: + name: {{ template "jenkins.fullname" . }}-auto-reload-config + - name: auto-reload-config-logs + emptyDir: {} + {{- end }} + {{- if .Values.controller.installPlugins }} + {{- if .Values.controller.overwritePluginsFromImage }} + - name: plugins + emptyDir: {} + {{- end }} + {{- end }} + {{- if and .Values.controller.initScripts .Values.controller.initConfigMap }} + - name: init-scripts + projected: + sources: + - configMap: + name: {{ template "jenkins.fullname" . }}-init-scripts + - configMap: + name: {{ .Values.controller.initConfigMap }} + {{- else if .Values.controller.initConfigMap }} + - name: init-scripts + configMap: + name: {{ .Values.controller.initConfigMap }} + {{- else if .Values.controller.initScripts }} + - name: init-scripts + configMap: + name: {{ template "jenkins.fullname" . }}-init-scripts + {{- end }} + - name: jenkins-config + configMap: + name: {{ template "jenkins.fullname" . }} + {{- if .Values.controller.installPlugins }} + - name: plugin-dir + emptyDir: {} + {{- end }} + {{- if or .Values.controller.additionalSecrets .Values.controller.existingSecret .Values.controller.additionalExistingSecrets .Values.controller.admin.createSecret }} + - name: jenkins-secrets + projected: + sources: + {{- if .Values.controller.additionalSecrets }} + - secret: + name: {{ template "jenkins.fullname" . }}-additional-secrets + {{- end }} + {{- if .Values.controller.additionalExistingSecrets }} + {{- range $key, $value := .Values.controller.additionalExistingSecrets }} + - secret: + name: {{ tpl $value.name $ }} + items: + - key: {{ tpl $value.keyName $ }} + path: {{ tpl $value.name $ }}-{{ tpl $value.keyName $ }} + {{- end }} + {{- end }} + {{- if .Values.controller.admin.createSecret }} + - secret: + name: {{ .Values.controller.admin.existingSecret | default (include "jenkins.fullname" .) }} + items: + - key: {{ .Values.controller.admin.userKey | default "jenkins-admin-user" }} + path: chart-admin-username + - key: {{ .Values.controller.admin.passwordKey | default "jenkins-admin-password" }} + path: chart-admin-password + {{- end }} + {{- if .Values.controller.existingSecret }} + - secret: + name: {{ .Values.controller.existingSecret }} + {{- end }} + {{- end }} + - name: jenkins-cache + emptyDir: {} + {{- if not (contains "jenkins-home" (quote .Values.persistence.volumes)) }} + - name: jenkins-home + {{- if .Values.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.persistence.existingClaim | default (include "jenkins.fullname" .) }} + {{- else }} + emptyDir: {} + {{- end -}} + {{- end }} + - name: sc-config-volume + emptyDir: {} + - name: tmp-volume + emptyDir: {} + + {{- if and .Values.controller.httpsKeyStore.enable (not .Values.controller.httpsKeyStore.disableSecretMount) }} + - name: jenkins-https-keystore + secret: + secretName: {{ if .Values.controller.httpsKeyStore.jenkinsHttpsJksSecretName }} {{ .Values.controller.httpsKeyStore.jenkinsHttpsJksSecretName }} {{ else }} {{ template "jenkins.fullname" . }}-https-jks {{ end }} + items: + - key: {{ .Values.controller.httpsKeyStore.jenkinsHttpsJksSecretKey }} + path: {{ .Values.controller.httpsKeyStore.fileName }} + {{- end }} + +{{- if .Values.controller.imagePullSecretName }} + imagePullSecrets: + - name: {{ .Values.controller.imagePullSecretName }} +{{- end -}} diff --git a/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-svc.yaml b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-svc.yaml new file mode 100644 index 000000000..a83466ce3 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/jenkins-controller-svc.yaml @@ -0,0 +1,56 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{template "jenkins.fullname" . }} + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + {{- if .Values.controller.serviceLabels }} +{{ toYaml .Values.controller.serviceLabels | indent 4 }} + {{- end }} +{{- if .Values.controller.serviceAnnotations }} + annotations: +{{ toYaml .Values.controller.serviceAnnotations | indent 4 }} +{{- end }} +spec: + {{- if .Values.controller.serviceExternalTrafficPolicy }} + externalTrafficPolicy: {{.Values.controller.serviceExternalTrafficPolicy}} + {{- end }} + {{- if (and (eq .Values.controller.serviceType "ClusterIP") (not (empty .Values.controller.clusterIP))) }} + clusterIP: {{.Values.controller.clusterIP}} + {{- end }} + ports: + - port: {{.Values.controller.servicePort}} + name: http + targetPort: {{ .Values.controller.targetPort }} + {{- if (and (eq .Values.controller.serviceType "NodePort") (not (empty .Values.controller.nodePort))) }} + nodePort: {{.Values.controller.nodePort}} + {{- end }} +{{- range $index, $port := .Values.controller.extraPorts }} + - port: {{ $port.port }} + name: {{ $port.name }} + {{- if $port.targetPort }} + targetPort: {{ $port.targetPort }} + {{- else }} + targetPort: {{ $port.port }} + {{- end -}} +{{- end }} + selector: + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + type: {{.Values.controller.serviceType}} + {{if eq .Values.controller.serviceType "LoadBalancer"}} +{{- if .Values.controller.loadBalancerSourceRanges }} + loadBalancerSourceRanges: +{{ toYaml .Values.controller.loadBalancerSourceRanges | indent 4 }} +{{- end }} + {{if .Values.controller.loadBalancerIP}} + loadBalancerIP: {{.Values.controller.loadBalancerIP}} + {{end}} + {{end}} diff --git a/charts/jenkins/jenkins/5.5.0/templates/rbac.yaml b/charts/jenkins/jenkins/5.5.0/templates/rbac.yaml new file mode 100644 index 000000000..581cb8d48 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/rbac.yaml @@ -0,0 +1,149 @@ +{{ if .Values.rbac.create }} +{{- $serviceName := include "jenkins.fullname" . -}} + +# This role is used to allow Jenkins scheduling of agents via Kubernetes plugin. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ $serviceName }}-schedule-agents + namespace: {{ template "jenkins.agent.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +rules: +- apiGroups: [""] + resources: ["pods", "pods/exec", "pods/log", "persistentvolumeclaims", "events"] + verbs: ["get", "list", "watch"] +- apiGroups: [""] + resources: ["pods", "pods/exec", "persistentvolumeclaims"] + verbs: ["create", "delete", "deletecollection", "patch", "update"] + +--- + +# We bind the role to the Jenkins service account. The role binding is created in the namespace +# where the agents are supposed to run. +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ $serviceName }}-schedule-agents + namespace: {{ template "jenkins.agent.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ $serviceName }}-schedule-agents +subjects: +- kind: ServiceAccount + name: {{ template "jenkins.serviceAccountName" .}} + namespace: {{ template "jenkins.namespace" . }} + +--- + +{{- if .Values.rbac.readSecrets }} +# This is needed if you want to use https://jenkinsci.github.io/kubernetes-credentials-provider-plugin/ +# as it needs permissions to get/watch/list Secrets +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "jenkins.fullname" . }}-read-secrets + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "watch", "list"] + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ $serviceName }}-read-secrets + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "jenkins.fullname" . }}-read-secrets +subjects: + - kind: ServiceAccount + name: {{ template "jenkins.serviceAccountName" . }} + namespace: {{ template "jenkins.namespace" . }} + +--- +{{- end}} + +{{- if .Values.controller.sidecars.configAutoReload.enabled }} +# The sidecar container which is responsible for reloading configuration changes +# needs permissions to watch ConfigMaps +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "jenkins.fullname" . }}-casc-reload + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +rules: +- apiGroups: [""] + resources: ["configmaps"] + verbs: ["get", "watch", "list"] + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ $serviceName }}-watch-configmaps + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "jenkins.fullname" . }}-casc-reload +subjects: +- kind: ServiceAccount + name: {{ template "jenkins.serviceAccountName" . }} + namespace: {{ template "jenkins.namespace" . }} + +{{- end}} + +{{ end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/secret-additional.yaml b/charts/jenkins/jenkins/5.5.0/templates/secret-additional.yaml new file mode 100644 index 000000000..d1908aa9b --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/secret-additional.yaml @@ -0,0 +1,21 @@ +{{- if .Values.controller.additionalSecrets -}} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "jenkins.fullname" . }}-additional-secrets + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +type: Opaque +data: +{{- range .Values.controller.additionalSecrets }} + {{ .name }}: {{ .value | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/secret-claims.yaml b/charts/jenkins/jenkins/5.5.0/templates/secret-claims.yaml new file mode 100644 index 000000000..e8b6d6c8e --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/secret-claims.yaml @@ -0,0 +1,29 @@ +{{- if .Values.controller.secretClaims -}} +{{- $r := .Release -}} +{{- $v := .Values -}} +{{- $chart := printf "%s-%s" .Chart.Name .Chart.Version -}} +{{- $namespace := include "jenkins.namespace" . -}} +{{- $serviceName := include "jenkins.fullname" . -}} +{{ range .Values.controller.secretClaims }} +--- +kind: SecretClaim +apiVersion: vaultproject.io/v1 +metadata: + name: {{ $serviceName }}-{{ .name | default .path | lower }} + namespace: {{ $namespace }} + labels: + "app.kubernetes.io/name": '{{ $serviceName }}' + {{- if $v.renderHelmLabels }} + "helm.sh/chart": "{{ $chart }}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ $r.Service }}" + "app.kubernetes.io/instance": "{{ $r.Name }}" + "app.kubernetes.io/component": "{{ $v.controller.componentName }}" +spec: + type: {{ .type | default "Opaque" }} + path: {{ .path }} +{{- if .renew }} + renew: {{ .renew }} +{{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/jenkins/jenkins/5.5.0/templates/secret-https-jks.yaml b/charts/jenkins/jenkins/5.5.0/templates/secret-https-jks.yaml new file mode 100644 index 000000000..5348de41e --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/secret-https-jks.yaml @@ -0,0 +1,20 @@ +{{- if and .Values.controller.httpsKeyStore.enable ( not .Values.controller.httpsKeyStore.jenkinsHttpsJksSecretName ) (not .Values.controller.httpsKeyStore.disableSecretMount) -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "jenkins.fullname" . }}-https-jks + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +type: Opaque +data: + jenkins-jks-file: | +{{ .Values.controller.httpsKeyStore.jenkinsKeyStoreBase64Encoded | indent 4 }} + https-jks-password: {{ .Values.controller.httpsKeyStore.password | b64enc }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/secret.yaml b/charts/jenkins/jenkins/5.5.0/templates/secret.yaml new file mode 100644 index 000000000..cc6ace179 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/secret.yaml @@ -0,0 +1,20 @@ +{{- if and (not .Values.controller.admin.existingSecret) (.Values.controller.admin.createSecret) -}} + +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "jenkins.fullname" . }} + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +type: Opaque +data: + jenkins-admin-password: {{ template "jenkins.password" . }} + jenkins-admin-user: {{ .Values.controller.admin.username | b64enc | quote }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/service-account-agent.yaml b/charts/jenkins/jenkins/5.5.0/templates/service-account-agent.yaml new file mode 100644 index 000000000..48f08ba6c --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/service-account-agent.yaml @@ -0,0 +1,26 @@ +{{ if .Values.serviceAccountAgent.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "jenkins.serviceAccountAgentName" . }} + namespace: {{ template "jenkins.agent.namespace" . }} +{{- if .Values.serviceAccountAgent.annotations }} + annotations: +{{ tpl (toYaml .Values.serviceAccountAgent.annotations) . | indent 4 }} +{{- end }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +{{- if .Values.serviceAccountAgent.extraLabels }} +{{ tpl (toYaml .Values.serviceAccountAgent.extraLabels) . | indent 4 }} +{{- end }} +{{- if .Values.serviceAccountAgent.imagePullSecretName }} +imagePullSecrets: + - name: {{ .Values.serviceAccountAgent.imagePullSecretName }} +{{- end -}} +{{ end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/service-account.yaml b/charts/jenkins/jenkins/5.5.0/templates/service-account.yaml new file mode 100644 index 000000000..b44eb488c --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/service-account.yaml @@ -0,0 +1,26 @@ +{{ if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "jenkins.serviceAccountName" . }} + namespace: {{ template "jenkins.namespace" . }} +{{- if .Values.serviceAccount.annotations }} + annotations: +{{ tpl (toYaml .Values.serviceAccount.annotations) . | indent 4 }} +{{- end }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +{{- if .Values.serviceAccount.extraLabels }} +{{ tpl (toYaml .Values.serviceAccount.extraLabels) . | indent 4 }} +{{- end }} +{{- if .Values.serviceAccount.imagePullSecretName }} +imagePullSecrets: + - name: {{ .Values.serviceAccount.imagePullSecretName }} +{{- end -}} +{{ end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/tests/jenkins-test.yaml b/charts/jenkins/jenkins/5.5.0/templates/tests/jenkins-test.yaml new file mode 100644 index 000000000..12a935ecc --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/tests/jenkins-test.yaml @@ -0,0 +1,49 @@ +{{- if .Values.controller.testEnabled }} +apiVersion: v1 +kind: Pod +metadata: + name: "{{ .Release.Name }}-ui-test-{{ randAlphaNum 5 | lower }}" + namespace: {{ template "jenkins.namespace" . }} + annotations: + "helm.sh/hook": test-success +spec: + {{- if .Values.controller.nodeSelector }} + nodeSelector: +{{ toYaml .Values.controller.nodeSelector | indent 4 }} + {{- end }} + {{- if .Values.controller.tolerations }} + tolerations: +{{ toYaml .Values.controller.tolerations | indent 4 }} + {{- end }} + initContainers: + - name: "test-framework" + image: "{{ .Values.helmtest.bats.image.registry }}/{{ .Values.helmtest.bats.image.repository }}:{{ .Values.helmtest.bats.image.tag }}" + command: + - "bash" + - "-c" + args: + - | + # copy bats to tools dir + set -ex + cp -R /opt/bats /tools/bats/ + volumeMounts: + - mountPath: /tools + name: tools + containers: + - name: {{ .Release.Name }}-ui-test + image: "{{ .Values.controller.image.registry }}/{{ .Values.controller.image.repository }}:{{- include "controller.image.tag" . -}}" + command: ["/tools/bats/bin/bats", "-t", "/tests/run.sh"] + volumeMounts: + - mountPath: /tests + name: tests + readOnly: true + - mountPath: /tools + name: tools + volumes: + - name: tests + configMap: + name: {{ template "jenkins.fullname" . }}-tests + - name: tools + emptyDir: {} + restartPolicy: Never +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/templates/tests/test-config.yaml b/charts/jenkins/jenkins/5.5.0/templates/tests/test-config.yaml new file mode 100644 index 000000000..12c5b3a0d --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/templates/tests/test-config.yaml @@ -0,0 +1,14 @@ +{{- if .Values.controller.testEnabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "jenkins.fullname" . }}-tests + namespace: {{ template "jenkins.namespace" . }} + annotations: + "helm.sh/hook": test +data: + run.sh: |- + @test "Testing Jenkins UI is accessible" { + curl --retry 48 --retry-delay 10 {{ template "jenkins.fullname" . }}:{{ .Values.controller.servicePort }}{{ default "" .Values.controller.jenkinsUriPrefix }}/login + } +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.0/values.yaml b/charts/jenkins/jenkins/5.5.0/values.yaml new file mode 100644 index 000000000..3407cbe77 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.0/values.yaml @@ -0,0 +1,1337 @@ +# Default values for jenkins. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name: value + +## Overrides for generated resource names +# See templates/_helpers.tpl +# -- Override the resource name prefix +# @default -- `Chart.Name` +nameOverride: +# -- Override the full resource names +# @default -- `jenkins-(release-name)` or `jenkins` if the release-name is `jenkins` +fullnameOverride: +# -- Override the deployment namespace +# @default -- `Release.Namespace` +namespaceOverride: + +# For FQDN resolving of the controller service. Change this value to match your existing configuration. +# ref: https://github.com/kubernetes/dns/blob/master/docs/specification.md +# -- Override the cluster name for FQDN resolving +clusterZone: "cluster.local" + +# -- The URL of the Kubernetes API server +kubernetesURL: "https://kubernetes.default" + +# -- The Jenkins credentials to access the Kubernetes API server. For the default cluster it is not needed. +credentialsId: + +# -- Enables rendering of the helm.sh/chart label to the annotations +renderHelmLabels: true + +controller: + # -- Used for label app.kubernetes.io/component + componentName: "jenkins-controller" + image: + # -- Controller image registry + registry: "docker.io" + # -- Controller image repository + repository: "jenkins/jenkins" + + # -- Controller image tag override; i.e., tag: "2.440.1-jdk17" + tag: + + # -- Controller image tag label + tagLabel: jdk17 + # -- Controller image pull policy + pullPolicy: "Always" + # -- Controller image pull secret + imagePullSecretName: + # -- Lifecycle specification for controller-container + lifecycle: {} + # postStart: + # exec: + # command: + # - "uname" + # - "-a" + + # -- Disable use of remember me + disableRememberMe: false + + # -- Set Number of executors + numExecutors: 0 + + # -- Sets the executor mode of the Jenkins node. Possible values are "NORMAL" or "EXCLUSIVE" + executorMode: "NORMAL" + + # -- Append Jenkins labels to the controller + customJenkinsLabels: [] + + hostNetworking: false + + # When enabling LDAP or another non-Jenkins identity source, the built-in admin account will no longer exist. + # If you disable the non-Jenkins identity store and instead use the Jenkins internal one, + # you should revert controller.admin.username to your preferred admin user: + admin: + + # -- Admin username created as a secret if `controller.admin.createSecret` is true + username: "admin" + # -- Admin password created as a secret if `controller.admin.createSecret` is true + # @default -- + password: + + # -- The key in the existing admin secret containing the username + userKey: jenkins-admin-user + # -- The key in the existing admin secret containing the password + passwordKey: jenkins-admin-password + + # The default configuration uses this secret to configure an admin user + # If you don't need that user or use a different security realm, then you can disable it + # -- Create secret for admin user + createSecret: true + + # -- The name of an existing secret containing the admin credentials + existingSecret: "" + # -- Email address for the administrator of the Jenkins instance + jenkinsAdminEmail: + + # This value should not be changed unless you use your custom image of jenkins or any derived from. + # If you want to use Cloudbees Jenkins Distribution docker, you should set jenkinsHome: "/var/cloudbees-jenkins-distribution" + # -- Custom Jenkins home path + jenkinsHome: "/var/jenkins_home" + + # This value should not be changed unless you use your custom image of jenkins or any derived from. + # If you want to use Cloudbees Jenkins Distribution docker, you should set jenkinsRef: "/usr/share/cloudbees-jenkins-distribution/ref" + # -- Custom Jenkins reference path + jenkinsRef: "/usr/share/jenkins/ref" + + # Path to the jenkins war file which is used by jenkins-plugin-cli. + jenkinsWar: "/usr/share/jenkins/jenkins.war" + # Override the default arguments passed to the war + # overrideArgs: + # - --httpPort=8080 + + # -- Resource allocation (Requests and Limits) + resources: + requests: + cpu: "50m" + memory: "256Mi" + limits: + cpu: "2000m" + memory: "4096Mi" + + # Share process namespace to allow sidecar containers to interact with processes in other containers in the same pod + shareProcessNamespace: false + + # Overrides the init container default values + # -- Resources allocation (Requests and Limits) for Init Container + initContainerResources: {} + # initContainerResources: + # requests: + # cpu: "50m" + # memory: "256Mi" + # limits: + # cpu: "2000m" + # memory: "4096Mi" + # -- Environment variable sources for Init Container + initContainerEnvFrom: [] + + # useful for i.e., http_proxy + # -- Environment variables for Init Container + initContainerEnv: [] + # initContainerEnv: + # - name: http_proxy + # value: "http://192.168.64.1:3128" + + # -- Environment variable sources for Jenkins Container + containerEnvFrom: [] + + # -- Environment variables for Jenkins Container + containerEnv: [] + # - name: http_proxy + # value: "http://192.168.64.1:3128" + + # Set min/max heap here if needed with "-Xms512m -Xmx512m" + # -- Append to `JAVA_OPTS` env var + javaOpts: + # -- Append to `JENKINS_OPTS` env var + jenkinsOpts: + + # If you are using the ingress definitions provided by this chart via the `controller.ingress` block, + # the configured hostname will be the ingress hostname starting with `https://` + # or `http://` depending on the `tls` configuration. + # The Protocol can be overwritten by specifying `controller.jenkinsUrlProtocol`. + # -- Set protocol for Jenkins URL; `https` if `controller.ingress.tls`, `http` otherwise + jenkinsUrlProtocol: + + # -- Set Jenkins URL if you are not using the ingress definitions provided by the chart + jenkinsUrl: + + # If you set this prefix and use ingress controller, then you might want to set the ingress path below + # I.e., "/jenkins" + # -- Root URI Jenkins will be served on + jenkinsUriPrefix: + + # -- Enable pod security context (must be `true` if podSecurityContextOverride, runAsUser or fsGroup are set) + usePodSecurityContext: true + + # Note that `runAsUser`, `fsGroup`, and `securityContextCapabilities` are + # being deprecated and replaced by `podSecurityContextOverride`. + # Set runAsUser to 1000 to let Jenkins run as non-root user 'jenkins', which exists in 'jenkins/jenkins' docker image. + # When configuring runAsUser to a different value than 0 also set fsGroup to the same value: + # -- Deprecated in favor of `controller.podSecurityContextOverride`. uid that jenkins runs with. + runAsUser: 1000 + + # -- Deprecated in favor of `controller.podSecurityContextOverride`. uid that will be used for persistent volume. + fsGroup: 1000 + + # If you have PodSecurityPolicies that require dropping of capabilities as suggested by CIS K8s benchmark, put them here + # securityContextCapabilities: + # drop: + # - NET_RAW + securityContextCapabilities: {} + + # In the case of mounting an ext4 filesystem, it might be desirable to use `supplementalGroups` instead of `fsGroup` in + # the `securityContext` block: https://github.com/kubernetes/kubernetes/issues/67014#issuecomment-589915496 + # podSecurityContextOverride: + # runAsUser: 1000 + # runAsNonRoot: true + # supplementalGroups: [1000] + # capabilities: {} + # -- Completely overwrites the contents of the pod security context, ignoring the values provided for `runAsUser`, `fsGroup`, and `securityContextCapabilities` + podSecurityContextOverride: ~ + + # -- Allow controlling the securityContext for the jenkins container + containerSecurityContext: + runAsUser: 1000 + runAsGroup: 1000 + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + + # For minikube, set this to NodePort, elsewhere uses LoadBalancer + # Use ClusterIP if your setup includes ingress controller + # -- k8s service type + serviceType: ClusterIP + + # -- k8s service clusterIP. Only used if serviceType is ClusterIP + clusterIp: + # -- k8s service port + servicePort: 8080 + # -- k8s target port + targetPort: 8080 + # -- k8s node port. Only used if serviceType is NodePort + nodePort: + + # Use Local to preserve the client source IP and avoids a second hop for LoadBalancer and NodePort type services, + # but risks potentially imbalanced traffic spreading. + serviceExternalTrafficPolicy: + + # -- Jenkins controller service annotations + serviceAnnotations: {} + # -- Jenkins controller custom labels for the StatefulSet + statefulSetLabels: {} + # foo: bar + # bar: foo + # -- Labels for the Jenkins controller-service + serviceLabels: {} + # service.beta.kubernetes.io/aws-load-balancer-backend-protocol: https + + # Put labels on Jenkins controller pod + # -- Custom Pod labels (an object with `label-key: label-value` pairs) + podLabels: {} + + # Enable Kubernetes Startup, Liveness and Readiness Probes + # if Startup Probe is supported, enable it too + # ~ 2 minutes to allow Jenkins to restart when upgrading plugins. Set ReadinessTimeout to be shorter than LivenessTimeout. + # ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes + # -- Enable Kubernetes Probes configuration configured in `controller.probes` + healthProbes: true + + probes: + startupProbe: + # -- Set the failure threshold for the startup probe + failureThreshold: 12 + httpGet: + # -- Set the Pod's HTTP path for the startup probe + path: '{{ default "" .Values.controller.jenkinsUriPrefix }}/login' + # -- Set the Pod's HTTP port to use for the startup probe + port: http + # -- Set the time interval between two startup probes executions in seconds + periodSeconds: 10 + # -- Set the timeout for the startup probe in seconds + timeoutSeconds: 5 + + livenessProbe: + # -- Set the failure threshold for the liveness probe + failureThreshold: 5 + httpGet: + # -- Set the Pod's HTTP path for the liveness probe + path: '{{ default "" .Values.controller.jenkinsUriPrefix }}/login' + # -- Set the Pod's HTTP port to use for the liveness probe + port: http + # -- Set the time interval between two liveness probes executions in seconds + periodSeconds: 10 + # -- Set the timeout for the liveness probe in seconds + timeoutSeconds: 5 + + # If Startup Probe is not supported on your Kubernetes cluster, you might want to use "initialDelaySeconds" instead. + # It delays the initial liveness probe while Jenkins is starting + # -- Set the initial delay for the liveness probe in seconds + initialDelaySeconds: + + readinessProbe: + # -- Set the failure threshold for the readiness probe + failureThreshold: 3 + httpGet: + # -- Set the Pod's HTTP path for the liveness probe + path: '{{ default "" .Values.controller.jenkinsUriPrefix }}/login' + # -- Set the Pod's HTTP port to use for the readiness probe + port: http + # -- Set the time interval between two readiness probes executions in seconds + periodSeconds: 10 + # -- Set the timeout for the readiness probe in seconds + timeoutSeconds: 5 + + # If Startup Probe is not supported on your Kubernetes cluster, you might want to use "initialDelaySeconds" instead. + # It delays the initial readiness probe while Jenkins is starting + # -- Set the initial delay for the readiness probe in seconds + initialDelaySeconds: + + # PodDisruptionBudget config + podDisruptionBudget: + # ref: https://kubernetes.io/docs/tasks/run-application/configure-pdb/ + + # -- Enable Kubernetes Pod Disruption Budget configuration + enabled: false + + # For Kubernetes v1.5+, use 'policy/v1beta1' + # For Kubernetes v1.21+, use 'policy/v1' + # -- Policy API version + apiVersion: "policy/v1beta1" + + annotations: {} + labels: {} + # -- Number of pods that can be unavailable. Either an absolute number or a percentage + maxUnavailable: "0" + + # -- Create Agent listener service + agentListenerEnabled: true + # -- Listening port for agents + agentListenerPort: 50000 + # -- Host port to listen for agents + agentListenerHostPort: + # -- Node port to listen for agents + agentListenerNodePort: + + # ref: https://kubernetes.io/docs/concepts/services-networking/service/#traffic-policies + # -- Traffic Policy of for the agentListener service + agentListenerExternalTrafficPolicy: + # -- Allowed inbound IP for the agentListener service + agentListenerLoadBalancerSourceRanges: + - 0.0.0.0/0 + # -- Disabled agent protocols + disabledAgentProtocols: + - JNLP-connect + - JNLP2-connect + csrf: + defaultCrumbIssuer: + # -- Enable the default CSRF Crumb issuer + enabled: true + # -- Enable proxy compatibility + proxyCompatability: true + + # Kubernetes service type for the JNLP agent service + # agentListenerServiceType is the Kubernetes Service type for the JNLP agent service, + # either 'LoadBalancer', 'NodePort', or 'ClusterIP' + # Note if you set this to 'LoadBalancer', you *must* define annotations to secure it. By default, + # this will be an external load balancer and allowing inbound 0.0.0.0/0, a HUGE + # security risk: https://github.com/kubernetes/charts/issues/1341 + # -- Defines how to expose the agentListener service + agentListenerServiceType: "ClusterIP" + + # -- Annotations for the agentListener service + agentListenerServiceAnnotations: {} + + # Optionally, assign an IP to the LoadBalancer agentListenerService LoadBalancer + # GKE users: only regional static IPs will work for Service Load balancer. + # -- Static IP for the agentListener LoadBalancer + agentListenerLoadBalancerIP: + + # -- Whether legacy remoting security should be enabled + legacyRemotingSecurityEnabled: false + + # Example of a 'LoadBalancer'-type agent listener with annotations securing it + # agentListenerServiceType: LoadBalancer + # agentListenerServiceAnnotations: + # service.beta.kubernetes.io/aws-load-balancer-internal: "True" + # service.beta.kubernetes.io/load-balancer-source-ranges: "172.0.0.0/8, 10.0.0.0/8" + + # LoadBalancerSourcesRange is a list of allowed CIDR values, which are combined with ServicePort to + # set allowed inbound rules on the security group assigned to the controller load balancer + # -- Allowed inbound IP addresses + loadBalancerSourceRanges: + - 0.0.0.0/0 + + # -- Optionally assign a known public LB IP + loadBalancerIP: + + # Optionally configure a JMX port. This requires additional javaOpts, for example, + # javaOpts: > + # -Dcom.sun.management.jmxremote.port=4000 + # -Dcom.sun.management.jmxremote.authenticate=false + # -Dcom.sun.management.jmxremote.ssl=false + # jmxPort: 4000 + # -- Open a port, for JMX stats + jmxPort: + + # -- Optionally configure other ports to expose in the controller container + extraPorts: [] + # - name: BuildInfoProxy + # port: 9000 + # targetPort: 9010 (Optional: Use to explicitly set targetPort if different from port) + + # Plugins will be installed during Jenkins controller start + # -- List of Jenkins plugins to install. If you don't want to install plugins, set it to `false` + installPlugins: + - kubernetes:4253.v7700d91739e5 + - workflow-aggregator:600.vb_57cdd26fdd7 + - git:5.2.2 + - configuration-as-code:1836.vccda_4a_122a_a_e + + # If set to false, Jenkins will download the minimum required version of all dependencies. + # -- Download the minimum required version or latest version of all dependencies + installLatestPlugins: true + + # -- Set to true to download the latest version of any plugin that is requested to have the latest version + installLatestSpecifiedPlugins: false + + # -- List of plugins to install in addition to those listed in controller.installPlugins + additionalPlugins: [] + + # Without this; whenever the controller gets restarted (Evicted, etc.) it will fetch plugin updates that have the potential to cause breakage. + # Note that for this to work, `persistence.enabled` needs to be set to `true` + # -- Initialize only on first installation. Ensures plugins do not get updated inadvertently. Requires `persistence.enabled` to be set to `true` + initializeOnce: false + + # Enable to always override the installed plugins with the values of 'controller.installPlugins' on upgrade or redeployment. + # -- Overwrite installed plugins on start + overwritePlugins: false + + # Configures if plugins bundled with `controller.image` should be overwritten with the values of 'controller.installPlugins' on upgrade or redeployment. + # -- Overwrite plugins that are already installed in the controller image + overwritePluginsFromImage: true + + # Configures the restrictions for naming projects. Set this key to null or empty to skip it in the default config. + projectNamingStrategy: standard + + # Useful with ghprb plugin. The OWASP plugin is not installed by default, please update controller.installPlugins. + # -- Enable HTML parsing using OWASP Markup Formatter Plugin (antisamy-markup-formatter) + enableRawHtmlMarkupFormatter: false + + # This is ignored if enableRawHtmlMarkupFormatter is true + # -- Yaml of the markup formatter to use + markupFormatter: plainText + + # Used to approve a list of groovy functions in pipelines used the script-security plugin. Can be viewed under /scriptApproval + # -- List of groovy functions to approve + scriptApproval: [] + # - "method groovy.json.JsonSlurperClassic parseText java.lang.String" + # - "new groovy.json.JsonSlurperClassic" + + # -- Map of groovy init scripts to be executed during Jenkins controller start + initScripts: {} + # test: |- + # print 'adding global pipeline libraries, register properties, bootstrap jobs...' + # -- Name of the existing ConfigMap that contains init scripts + initConfigMap: + + # 'name' is a name of an existing secret in the same namespace as jenkins, + # 'keyName' is the name of one of the keys inside the current secret. + # the 'name' and 'keyName' are concatenated with a '-' in between, so for example: + # an existing secret "secret-credentials" and a key inside it named "github-password" should be used in JCasC as ${secret-credentials-github-password} + # 'name' and 'keyName' must be lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', + # and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc') + # existingSecret existing secret "secret-credentials" and a key inside it named "github-username" should be used in JCasC as ${github-username} + # When using existingSecret no need to specify the keyName under additionalExistingSecrets. + existingSecret: + + # -- List of additional existing secrets to mount + additionalExistingSecrets: [] + # ref: https://github.com/jenkinsci/configuration-as-code-plugin/blob/master/docs/features/secrets.adoc#kubernetes-secrets + # additionalExistingSecrets: + # - name: secret-name-1 + # keyName: username + # - name: secret-name-1 + # keyName: password + + # -- List of additional secrets to create and mount + additionalSecrets: [] + # ref: https://github.com/jenkinsci/configuration-as-code-plugin/blob/master/docs/features/secrets.adoc#kubernetes-secrets + # additionalSecrets: + # - name: nameOfSecret + # value: secretText + + # Generate SecretClaim resources to create Kubernetes secrets from HashiCorp Vault using kube-vault-controller. + # 'name' is the name of the secret that will be created in Kubernetes. The Jenkins fullname is prepended to this value. + # 'path' is the fully qualified path to the secret in Vault + # 'type' is an optional Kubernetes secret type. The default is 'Opaque' + # 'renew' is an optional secret renewal time in seconds + # -- List of `SecretClaim` resources to create + secretClaims: [] + # - name: secretName # required + # path: testPath # required + # type: kubernetes.io/tls # optional + # renew: 60 # optional + + # -- Name of default cloud configuration. + cloudName: "kubernetes" + + # Below is the implementation of Jenkins Configuration as Code. Add a key under configScripts for each configuration area, + # where each corresponds to a plugin or section of the UI. Each key (prior to | character) is just a label, and can be any value. + # Keys are only used to give the section a meaningful name. The only restriction is they may only contain RFC 1123 \ DNS label + # characters: lowercase letters, numbers, and hyphens. The keys become the name of a configuration yaml file on the controller in + # /var/jenkins_home/casc_configs (by default) and will be processed by the Configuration as Code Plugin. The lines after each | + # become the content of the configuration yaml file. The first line after this is a JCasC root element, e.g., jenkins, credentials, + # etc. Best reference is https:///configuration-as-code/reference. The example below creates a welcome message: + JCasC: + # -- Enables default Jenkins configuration via configuration as code plugin + defaultConfig: true + + # If true, the init container deletes all the plugin config files and Jenkins Config as Code overwrites any existing configuration + # -- Whether Jenkins Config as Code should overwrite any existing configuration + overwriteConfiguration: false + # -- Remote URLs for configuration files. + configUrls: [] + # - https://acme.org/jenkins.yaml + # -- List of Jenkins Config as Code scripts + configScripts: {} + # welcome-message: | + # jenkins: + # systemMessage: Welcome to our CI\CD server. This Jenkins is configured and managed 'as code'. + + # Allows adding to the top-level security JCasC section. For legacy purposes, by default, the chart includes apiToken configurations + # -- Jenkins Config as Code security-section + security: + apiToken: + creationOfLegacyTokenEnabled: false + tokenGenerationOnCreationEnabled: false + usageStatisticsEnabled: true + + # Ignored if securityRealm is defined in controller.JCasC.configScripts + # -- Jenkins Config as Code Security Realm-section + securityRealm: |- + local: + allowsSignup: false + enableCaptcha: false + users: + - id: "${chart-admin-username}" + name: "Jenkins Admin" + password: "${chart-admin-password}" + + # Ignored if authorizationStrategy is defined in controller.JCasC.configScripts + # -- Jenkins Config as Code Authorization Strategy-section + authorizationStrategy: |- + loggedInUsersCanDoAnything: + allowAnonymousRead: false + + # -- Annotations for the JCasC ConfigMap + configMapAnnotations: {} + + # -- Custom init-container specification in raw-yaml format + customInitContainers: [] + # - name: custom-init + # image: "alpine:3" + # imagePullPolicy: Always + # command: [ "uname", "-a" ] + + sidecars: + configAutoReload: + # If enabled: true, Jenkins Configuration as Code will be reloaded on-the-fly without a reboot. + # If false or not-specified, JCasC changes will cause a reboot and will only be applied at the subsequent start-up. + # Auto-reload uses the http:///reload-configuration-as-code endpoint to reapply config when changes to + # the configScripts are detected. + # -- Enables Jenkins Config as Code auto-reload + enabled: true + image: + # -- Registry for the image that triggers the reload + registry: docker.io + # -- Repository of the image that triggers the reload + repository: kiwigrid/k8s-sidecar + # -- Tag for the image that triggers the reload + tag: 1.27.5 + imagePullPolicy: IfNotPresent + resources: {} + # limits: + # cpu: 100m + # memory: 100Mi + # requests: + # cpu: 50m + # memory: 50Mi + # -- Enables additional volume mounts for the config auto-reload container + additionalVolumeMounts: [] + # - name: auto-reload-config + # mountPath: /var/config/logger + # - name: auto-reload-logs + # mountPath: /var/log/auto_reload + # -- Config auto-reload logging settings + logging: + # See default settings https://github.com/kiwigrid/k8s-sidecar/blob/master/src/logger.py + configuration: + # -- Enables custom log config utilizing using the settings below. + override: false + logLevel: INFO + formatter: JSON + logToConsole: true + logToFile: false + maxBytes: 1024 + backupCount: 3 + + # -- The scheme to use when connecting to the Jenkins configuration as code endpoint + scheme: http + # -- Skip TLS verification when connecting to the Jenkins configuration as code endpoint + skipTlsVerify: false + + # -- How many connection-related errors to retry on + reqRetryConnect: 10 + # -- How many seconds to wait before updating config-maps/secrets (sets METHOD=SLEEP on the sidecar) + sleepTime: + + # -- Environment variable sources for the Jenkins Config as Code auto-reload container + envFrom: [] + # -- Environment variables for the Jenkins Config as Code auto-reload container + env: {} + # - name: REQ_TIMEOUT + # value: "30" + + # SSH port value can be set to any unused TCP port. The default, 1044, is a non-standard SSH port that has been chosen at random. + # This is only used to reload JCasC config from the sidecar container running in the Jenkins controller pod. + # This TCP port will not be open in the pod (unless you specifically configure this), so Jenkins will not be + # accessible via SSH from outside the pod. Note if you use non-root pod privileges (runAsUser & fsGroup), + # this must be > 1024: + sshTcpPort: 1044 + # folder in the pod that should hold the collected dashboards: + folder: "/var/jenkins_home/casc_configs" + + # If specified, the sidecar will search for JCasC config-maps inside this namespace. + # Otherwise, the namespace in which the sidecar is running will be used. + # It's also possible to specify ALL to search in all namespaces: + # searchNamespace: + # -- Enable container security context + containerSecurityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + + # -- Configures additional sidecar container(s) for the Jenkins controller + additionalSidecarContainers: [] + ## The example below runs the client for https://smee.io as sidecar container next to Jenkins, + ## that allows triggering build behind a secure firewall. + ## https://jenkins.io/blog/2019/01/07/webhook-firewalls/#triggering-builds-with-webhooks-behind-a-secure-firewall + ## + ## Note: To use it you should go to https://smee.io/new and update the url to the generated one. + # - name: smee + # image: docker.io/twalter/smee-client:1.0.2 + # args: ["--port", "{{ .Values.controller.servicePort }}", "--path", "/github-webhook/", "--url", "https://smee.io/new"] + # resources: + # limits: + # cpu: 50m + # memory: 128Mi + # requests: + # cpu: 10m + # memory: 32Mi + + # -- Name of the Kubernetes scheduler to use + schedulerName: "" + + # ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector + # -- Node labels for pod assignment + nodeSelector: {} + + # ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#taints-and-tolerations-beta-feature + # -- Toleration labels for pod assignment + tolerations: [] + # -- Set TerminationGracePeriodSeconds + terminationGracePeriodSeconds: + # -- Set the termination message path + terminationMessagePath: + # -- Set the termination message policy + terminationMessagePolicy: + + # -- Affinity settings + affinity: {} + + # Leverage a priorityClass to ensure your pods survive resource shortages + # ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ + # -- The name of a `priorityClass` to apply to the controller pod + priorityClassName: + + # -- Annotations for controller pod + podAnnotations: {} + # -- Annotations for controller StatefulSet + statefulSetAnnotations: {} + + # ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies + # -- Update strategy for StatefulSet + updateStrategy: {} + + # -- Topology spread constraints + topologySpreadConstraints: {} + + ingress: + # -- Enables ingress + enabled: false + + # Override for the default paths that map requests to the backend + # -- Override for the default Ingress paths + paths: [] + # - backend: + # serviceName: ssl-redirect + # servicePort: use-annotation + # - backend: + # serviceName: >- + # {{ template "jenkins.fullname" . }} + # # Don't use string here, use only integer value! + # servicePort: 8080 + + # For Kubernetes v1.14+, use 'networking.k8s.io/v1beta1' + # For Kubernetes v1.19+, use 'networking.k8s.io/v1' + # -- Ingress API version + apiVersion: "extensions/v1beta1" + # -- Ingress labels + labels: {} + # -- Ingress annotations + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + # For Kubernetes >= 1.18 you should specify the ingress-controller via the field ingressClassName + # See https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/#specifying-the-class-of-an-ingress + # ingressClassName: nginx + + # Set this path to jenkinsUriPrefix above or use annotations to rewrite path + # -- Ingress path + path: + + # configures the hostname e.g. jenkins.example.com + # -- Ingress hostname + hostName: + # -- Hostname to serve assets from + resourceRootUrl: + # -- Ingress TLS configuration + tls: [] + # - secretName: jenkins.cluster.local + # hosts: + # - jenkins.cluster.local + + # often you want to have your controller all locked down and private, + # but you still want to get webhooks from your SCM + # A secondary ingress will let you expose different urls + # with a different configuration + secondaryingress: + enabled: false + # paths you want forwarded to the backend + # ex /github-webhook + paths: [] + # For Kubernetes v1.14+, use 'networking.k8s.io/v1beta1' + # For Kubernetes v1.19+, use 'networking.k8s.io/v1' + apiVersion: "extensions/v1beta1" + labels: {} + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + # For Kubernetes >= 1.18 you should specify the ingress-controller via the field ingressClassName + # See https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/#specifying-the-class-of-an-ingress + # ingressClassName: nginx + # configures the hostname e.g., jenkins-external.example.com + hostName: + tls: + # - secretName: jenkins-external.example.com + # hosts: + # - jenkins-external.example.com + + # If you're running on GKE and need to configure a backendconfig + # to finish ingress setup, use the following values. + # Docs: https://cloud.google.com/kubernetes-engine/docs/concepts/backendconfig + backendconfig: + # -- Enables backendconfig + enabled: false + # -- backendconfig API version + apiVersion: "extensions/v1beta1" + # -- backendconfig name + name: + # -- backendconfig labels + labels: {} + # -- backendconfig annotations + annotations: {} + # -- backendconfig spec + spec: {} + + # Openshift route + route: + # -- Enables openshift route + enabled: false + # -- Route labels + labels: {} + # -- Route annotations + annotations: {} + # -- Route path + path: + + # -- Allows for adding entries to Pod /etc/hosts + hostAliases: [] + # ref: https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ + # hostAliases: + # - ip: 192.168.50.50 + # hostnames: + # - something.local + # - ip: 10.0.50.50 + # hostnames: + # - other.local + + # Expose Prometheus metrics + prometheus: + # If enabled, add the prometheus plugin to the list of plugins to install + # https://plugins.jenkins.io/prometheus + + # -- Enables prometheus service monitor + enabled: false + # -- Additional labels to add to the service monitor object + serviceMonitorAdditionalLabels: {} + # -- Set a custom namespace where to deploy ServiceMonitor resource + serviceMonitorNamespace: + # -- How often prometheus should scrape metrics + scrapeInterval: 60s + + # Defaults to the default endpoint used by the prometheus plugin + # -- The endpoint prometheus should get metrics from + scrapeEndpoint: /prometheus + + # See here: https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/ + # The `groups` root object is added by default, add the rule entries + # -- Array of prometheus alerting rules + alertingrules: [] + # -- Additional labels to add to the PrometheusRule object + alertingRulesAdditionalLabels: {} + # -- Set a custom namespace where to deploy PrometheusRule resource + prometheusRuleNamespace: "" + + # RelabelConfigs to apply to samples before scraping. Prometheus Operator automatically adds + # relabelings for a few standard Kubernetes fields. The original scrape job’s name + # is available via the __tmp_prometheus_job_name label. + # More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config + relabelings: [] + # MetricRelabelConfigs to apply to samples before ingestion. + metricRelabelings: [] + + googlePodMonitor: + # If enabled, It creates Google Managed Prometheus scraping config + enabled: false + # Set a custom namespace where to deploy PodMonitoring resource + # serviceMonitorNamespace: "" + scrapeInterval: 60s + # This is the default endpoint used by the prometheus plugin + scrapeEndpoint: /prometheus + + # -- Can be used to disable rendering controller test resources when using helm template + testEnabled: true + + httpsKeyStore: + # -- Enables HTTPS keystore on jenkins controller + enable: false + # -- Name of the secret that already has ssl keystore + jenkinsHttpsJksSecretName: "" + # -- Name of the key in the secret that already has ssl keystore + jenkinsHttpsJksSecretKey: "jenkins-jks-file" + # -- Name of the secret that contains the JKS password, if it is not in the same secret as the JKS file + jenkinsHttpsJksPasswordSecretName: "" + # -- Name of the key in the secret that contains the JKS password + jenkinsHttpsJksPasswordSecretKey: "https-jks-password" + disableSecretMount: false + + # When HTTPS keystore is enabled, servicePort and targetPort will be used as HTTPS port + # -- HTTP Port that Jenkins should listen to along with HTTPS, it also serves as the liveness and readiness probes port. + httpPort: 8081 + # -- Path of HTTPS keystore file + path: "/var/jenkins_keystore" + # -- Jenkins keystore filename which will appear under controller.httpsKeyStore.path + fileName: "keystore.jks" + # -- Jenkins keystore password + password: "password" + + # -- Base64 encoded Keystore content. Keystore must be converted to base64 then being pasted here + jenkinsKeyStoreBase64Encoded: + # Convert keystore.jks files content to base64 > $ cat keystore.jks | base64 +# /u3+7QAAAAIAAAABAAAAAQANamVua2luc2NpLmNvbQAAAW2r/b1ZAAAFATCCBP0wDgYKKwYBBAEq +# AhEBAQUABIIE6QbCqasvoHS0pSwYqSvdydMCB9t+VNfwhFIiiuAelJfO5sSe2SebJbtwHgLcRz1Z +# gMtWgOSFdl3bWSzA7vrW2LED52h+jXLYSWvZzuDuh8hYO85m10ikF6QR+dTi4jra0whIFDvq3pxe +# TnESxEsN+DvbZM3jA3qsjQJSeISNpDjO099dqQvHpnCn18lyk7J4TWJ8sOQQb1EM2zDAfAOSqA/x +# QuPEFl74DlY+5DIk6EBvpmWhaMSvXzWZACGA0sYqa157dq7O0AqmuLG/EI5EkHETO4CrtBW+yLcy +# 2dUCXOMA+j+NjM1BjrQkYE5vtSfNO6lFZcISyKo5pTFlcA7ut0Fx2nZ8GhHTn32CpeWwNcZBn1gR +# pZVt6DxVVkhTAkMLhR4rL2wGIi/1WRs23ZOLGKtyDNvDHnQyDiQEoJGy9nAthA8aNHa3cfdF10vB +# Drb19vtpFHmpvKEEhpk2EBRF4fTi644Fuhu2Ied6118AlaPvEea+n6G4vBz+8RWuVCmZjLU+7h8l +# Hy3/WdUPoIL5eW7Kz+hS+sRTFzfu9C48dMkQH3a6f3wSY+mufizNF9U298r98TnYy+PfDJK0bstG +# Ph6yPWx8DGXKQBwrhWJWXI6JwZDeC5Ny+l8p1SypTmAjpIaSW3ge+KgcL6Wtt1R5hUV1ajVwVSUi +# HF/FachKqPqyLJFZTGjNrxnmNYpt8P1d5JTvJfmfr55Su/P9n7kcyWp7zMcb2Q5nlXt4tWogOHLI +# OzEWKCacbFfVHE+PpdrcvCVZMDzFogIq5EqGTOZe2poPpBVE+1y9mf5+TXBegy5HToLWvmfmJNTO +# NCDuBjgLs2tdw2yMPm4YEr57PnMX5gGTC3f2ZihXCIJDCRCdQ9sVBOjIQbOCzxFXkVITo0BAZhCi +# Yz61wt3Ud8e//zhXWCkCsSV+IZCxxPzhEFd+RFVjW0Nm9hsb2FgAhkXCjsGROgoleYgaZJWvQaAg +# UyBzMmKDPKTllBHyE3Gy1ehBNGPgEBChf17/9M+j8pcm1OmlM434ctWQ4qW7RU56//yq1soFY0Te +# fu2ei03a6m68fYuW6s7XEEK58QisJWRAvEbpwu/eyqfs7PsQ+zSgJHyk2rO95IxdMtEESb2GRuoi +# Bs+AHNdYFTAi+GBWw9dvEgqQ0Mpv0//6bBE/Fb4d7b7f56uUNnnE7mFnjGmGQN+MvC62pfwfvJTT +# EkT1iZ9kjM9FprTFWXT4UmO3XTvesGeE50sV9YPm71X4DCQwc4KE8vyuwj0s6oMNAUACW2ClU9QQ +# y0tRpaF1tzs4N42Q5zl0TzWxbCCjAtC3u6xf+c8MCGrr7DzNhm42LOQiHTa4MwX4x96q7235oiAU +# iQqSI/hyF5yLpWw4etyUvsx2/0/0wkuTU1FozbLoCWJEWcPS7QadMrRRISxHf0YobIeQyz34regl +# t1qSQ3dCU9D6AHLgX6kqllx4X0fnFq7LtfN7fA2itW26v+kAT2QFZ3qZhINGfofCja/pITC1uNAZ +# gsJaTMcQ600krj/ynoxnjT+n1gmeqThac6/Mi3YlVeRtaxI2InL82ZuD+w/dfY9OpPssQjy3xiQa +# jPuaMWXRxz/sS9syOoGVH7XBwKrWpQcpchozWJt40QV5DslJkclcr8aC2AGlzuJMTdEgz1eqV0+H +# bAXG9HRHN/0eJTn1/QAAAAEABVguNTA5AAADjzCCA4swggJzAhRGqVxH4HTLYPGO4rzHcCPeGDKn +# xTANBgkqhkiG9w0BAQsFADCBgTELMAkGA1UEBhMCY2ExEDAOBgNVBAgMB29udGFyaW8xEDAOBgNV +# BAcMB3Rvcm9udG8xFDASBgNVBAoMC2plbmtpbnN0ZXN0MRkwFwYDVQQDDBBqZW5raW5zdGVzdC5p +# bmZvMR0wGwYJKoZIhvcNAQkBFg50ZXN0QHRlc3QuaW5mbzAeFw0xOTEwMDgxNTI5NTVaFw0xOTEx +# MDcxNTI5NTVaMIGBMQswCQYDVQQGEwJjYTEQMA4GA1UECAwHb250YXJpbzEQMA4GA1UEBwwHdG9y +# b250bzEUMBIGA1UECgwLamVua2luc3Rlc3QxGTAXBgNVBAMMEGplbmtpbnN0ZXN0LmluZm8xHTAb +# BgkqhkiG9w0BCQEWDnRlc3RAdGVzdC5pbmZvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +# AQEA02q352JTHGvROMBhSHvSv+vnoOTDKSTz2aLQn0tYrIRqRo+8bfmMjXuhkwZPSnCpvUGNAJ+w +# Jrt/dqMoYUjCBkjylD/qHmnXN5EwS1cMg1Djh65gi5JJLFJ7eNcoSsr/0AJ+TweIal1jJSP3t3PF +# 9Uv21gm6xdm7HnNK66WpUUXLDTKaIs/jtagVY1bLOo9oEVeLN4nT2CYWztpMvdCyEDUzgEdDbmrP +# F5nKUPK5hrFqo1Dc5rUI4ZshL3Lpv398aMxv6n2adQvuL++URMEbXXBhxOrT6rCtYzbcR5fkwS9i +# d3Br45CoWOQro02JAepoU0MQKY5+xQ4Bq9Q7tB9BAwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAe +# 4xc+mSvKkrKBHg9/zpkWgZUiOp4ENJCi8H4tea/PCM439v6y/kfjT/okOokFvX8N5aa1OSz2Vsrl +# m8kjIc6hiA7bKzT6lb0EyjUShFFZ5jmGVP4S7/hviDvgB5yEQxOPpumkdRP513YnEGj/o9Pazi5h +# /MwpRxxazoda9r45kqQpyG+XoM4pB+Fd3JzMc4FUGxfVPxJU4jLawnJJiZ3vqiSyaB0YyUL+Er1Q +# 6NnqtR4gEBF0ZVlQmkycFvD4EC2boP943dLqNUvop+4R3SM1QMM6P5u8iTXtHd/VN4MwMyy1wtog +# hYAzODo1Jt59pcqqKJEas0C/lFJEB3frw4ImNx5fNlJYOpx+ijfQs9m39CevDq0= + +agent: + # -- Enable Kubernetes plugin jnlp-agent podTemplate + enabled: true + # -- The name of the pod template to use for providing default values + defaultsProviderTemplate: "" + + # For connecting to the Jenkins controller + # -- Overrides the Kubernetes Jenkins URL + jenkinsUrl: + + # connects to the specified host and port, instead of connecting directly to the Jenkins controller + # -- Overrides the Kubernetes Jenkins tunnel + jenkinsTunnel: + # -- Disables the verification of the controller certificate on remote connection. This flag correspond to the "Disable https certificate check" flag in kubernetes plugin UI + skipTlsVerify: false + # -- Enable the possibility to restrict the usage of this agent to specific folder. This flag correspond to the "Restrict pipeline support to authorized folders" flag in kubernetes plugin UI + usageRestricted: false + # -- The connection timeout in seconds for connections to Kubernetes API. The minimum value is 5 + kubernetesConnectTimeout: 5 + # -- The read timeout in seconds for connections to Kubernetes API. The minimum value is 15 + kubernetesReadTimeout: 15 + # -- The maximum concurrent connections to Kubernetes API + maxRequestsPerHostStr: "32" + # -- Time in minutes after which the Kubernetes cloud plugin will clean up an idle worker that has not already terminated + retentionTimeout: 5 + # -- Seconds to wait for pod to be running + waitForPodSec: 600 + # -- Namespace in which the Kubernetes agents should be launched + namespace: + # -- Custom Pod labels (an object with `label-key: label-value` pairs) + podLabels: {} + # -- Custom registry used to pull the agent jnlp image from + jnlpregistry: + image: + # -- Repository to pull the agent jnlp image from + repository: "jenkins/inbound-agent" + # -- Tag of the image to pull + tag: "3256.v88a_f6e922152-1" + # -- Configure working directory for default agent + workingDir: "/home/jenkins/agent" + nodeUsageMode: "NORMAL" + # -- Append Jenkins labels to the agent + customJenkinsLabels: [] + # -- Name of the secret to be used to pull the image + imagePullSecretName: + componentName: "jenkins-agent" + # -- Enables agent communication via websockets + websocket: false + directConnection: false + # -- Agent privileged container + privileged: false + # -- Configure container user + runAsUser: + # -- Configure container group + runAsGroup: + # -- Enables the agent to use the host network + hostNetworking: false + # -- Resources allocation (Requests and Limits) + resources: + requests: + cpu: "512m" + memory: "512Mi" + # ephemeralStorage: + limits: + cpu: "512m" + memory: "512Mi" + # ephemeralStorage: + livenessProbe: {} +# execArgs: "cat /tmp/healthy" +# failureThreshold: 3 +# initialDelaySeconds: 0 +# periodSeconds: 10 +# successThreshold: 1 +# timeoutSeconds: 1 + + # You may want to change this to true while testing a new image + # -- Always pull agent container image before build + alwaysPullImage: false + # When using Pod Security Admission in the Agents namespace with the restricted Pod Security Standard, + # the jnlp container cannot be scheduled without overriding its container definition with a securityContext. + # This option allows to automatically inject in the jnlp container a securityContext + # that is suitable for the use of the restricted Pod Security Standard. + # -- Set a restricted securityContext on jnlp containers + restrictedPssSecurityContext: false + # Controls how agent pods are retained after the Jenkins build completes + # Possible values: Always, Never, OnFailure + podRetention: "Never" + # Disable if you do not want the Yaml the agent pod template to show up + # in the job Console Output. This can be helpful for either security reasons + # or simply to clean up the output to make it easier to read. + showRawYaml: true + + # You can define the volumes that you want to mount for this container + # Allowed types are: ConfigMap, EmptyDir, EphemeralVolume, HostPath, Nfs, PVC, Secret + # Configure the attributes as they appear in the corresponding Java class for that type + # https://github.com/jenkinsci/kubernetes-plugin/tree/master/src/main/java/org/csanchez/jenkins/plugins/kubernetes/volumes + # -- Additional volumes + volumes: [] + # - type: ConfigMap + # configMapName: myconfigmap + # mountPath: /var/myapp/myconfigmap + # - type: EmptyDir + # mountPath: /var/myapp/myemptydir + # memory: false + # - type: EphemeralVolume + # mountPath: /var/myapp/myephemeralvolume + # accessModes: ReadWriteOnce + # requestsSize: 10Gi + # storageClassName: mystorageclass + # - type: HostPath + # hostPath: /var/lib/containers + # mountPath: /var/myapp/myhostpath + # - type: Nfs + # mountPath: /var/myapp/mynfs + # readOnly: false + # serverAddress: "192.0.2.0" + # serverPath: /var/lib/containers + # - type: PVC + # claimName: mypvc + # mountPath: /var/myapp/mypvc + # readOnly: false + # - type: Secret + # defaultMode: "600" + # mountPath: /var/myapp/mysecret + # secretName: mysecret + # Pod-wide environment, these vars are visible to any container in the agent pod + + # You can define the workspaceVolume that you want to mount for this container + # Allowed types are: DynamicPVC, EmptyDir, EphemeralVolume, HostPath, Nfs, PVC + # Configure the attributes as they appear in the corresponding Java class for that type + # https://github.com/jenkinsci/kubernetes-plugin/tree/master/src/main/java/org/csanchez/jenkins/plugins/kubernetes/volumes/workspace + # -- Workspace volume (defaults to EmptyDir) + workspaceVolume: {} + ## DynamicPVC example + # - type: DynamicPVC + # configMapName: myconfigmap + ## EmptyDir example + # - type: EmptyDir + # memory: false + ## EphemeralVolume example + # - type: EphemeralVolume + # accessModes: ReadWriteOnce + # requestsSize: 10Gi + # storageClassName: mystorageclass + ## HostPath example + # - type: HostPath + # hostPath: /var/lib/containers + ## NFS example + # - type: Nfs + # readOnly: false + # serverAddress: "192.0.2.0" + # serverPath: /var/lib/containers + ## PVC example + # - type: PVC + # claimName: mypvc + # readOnly: false + + # Pod-wide environment, these vars are visible to any container in the agent pod + # -- Environment variables for the agent Pod + envVars: [] + # - name: PATH + # value: /usr/local/bin + # -- Mount a secret as environment variable + secretEnvVars: [] + # - key: PATH + # optional: false # default: false + # secretKey: MY-K8S-PATH + # secretName: my-k8s-secret + + # -- Node labels for pod assignment + nodeSelector: {} + # Key Value selectors. Ex: + # nodeSelector + # jenkins-agent: v1 + + # -- Command to execute when side container starts + command: + # -- Arguments passed to command to execute + args: "${computer.jnlpmac} ${computer.name}" + # -- Side container name + sideContainerName: "jnlp" + + # Doesn't allocate pseudo TTY by default + # -- Allocate pseudo tty to the side container + TTYEnabled: false + # -- Max number of agents to launch + containerCap: 10 + # -- Agent Pod base name + podName: "default" + + # -- Allows the Pod to remain active for reuse until the configured number of minutes has passed since the last step was executed on it + idleMinutes: 0 + + + # The raw yaml of a Pod API Object, for example, this allows usage of toleration for agent pods. + # https://github.com/jenkinsci/kubernetes-plugin#using-yaml-to-define-pod-templates + # https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + # -- The raw yaml of a Pod API Object to merge into the agent spec + yamlTemplate: "" + # yamlTemplate: |- + # apiVersion: v1 + # kind: Pod + # spec: + # tolerations: + # - key: "key" + # operator: "Equal" + # value: "value" + + # -- Defines how the raw yaml field gets merged with yaml definitions from inherited pod templates. Possible values: "merge" or "override" + yamlMergeStrategy: "override" + # -- Controls whether the defined yaml merge strategy will be inherited if another defined pod template is configured to inherit from the current one + inheritYamlMergeStrategy: false + # -- Timeout in seconds for an agent to be online + connectTimeout: 100 + # -- Annotations to apply to the pod + annotations: {} + + # Containers specified here are added to all agents. Set key empty to remove container from additional agents. + # -- Add additional containers to the agents + additionalContainers: [] + # - sideContainerName: dind + # image: + # repository: docker + # tag: dind + # command: dockerd-entrypoint.sh + # args: "" + # privileged: true + # resources: + # requests: + # cpu: 500m + # memory: 1Gi + # limits: + # cpu: 1 + # memory: 2Gi + + # Useful when configuring agents only with the podTemplates value, since the default podTemplate populated by values mentioned above will be excluded in the rendered template. + # -- Disable the default Jenkins Agent configuration + disableDefaultAgent: false + + # Below is the implementation of custom pod templates for the default configured kubernetes cloud. + # Add a key under podTemplates for each pod template. Each key (prior to | character) is just a label, and can be any value. + # Keys are only used to give the pod template a meaningful name. The only restriction is they may only contain RFC 1123 \ DNS label + # characters: lowercase letters, numbers, and hyphens. Each pod template can contain multiple containers. + # For this pod templates configuration to be loaded, the following values must be set: + # controller.JCasC.defaultConfig: true + # Best reference is https:///configuration-as-code/reference#Cloud-kubernetes. The example below creates a python pod template. + # -- Configures extra pod templates for the default kubernetes cloud + podTemplates: {} + # python: | + # - name: python + # label: jenkins-python + # serviceAccount: jenkins + # containers: + # - name: python + # image: python:3 + # command: "/bin/sh -c" + # args: "cat" + # ttyEnabled: true + # privileged: true + # resourceRequestCpu: "400m" + # resourceRequestMemory: "512Mi" + # resourceLimitCpu: "1" + # resourceLimitMemory: "1024Mi" + +# Inherits all values from `agent` so you only need to specify values which differ +# -- Configure additional +additionalAgents: {} +# maven: +# podName: maven +# customJenkinsLabels: maven +# # An example of overriding the jnlp container +# # sideContainerName: jnlp +# image: +# repository: jenkins/jnlp-agent-maven +# tag: latest +# python: +# podName: python +# customJenkinsLabels: python +# sideContainerName: python +# image: +# repository: python +# tag: "3" +# command: "/bin/sh -c" +# args: "cat" +# TTYEnabled: true + +# Here you can add additional clouds +# They inherit all values from the default cloud (including the main agent), so +# you only need to specify values which differ. If you want to override +# default additionalAgents with the additionalClouds.additionalAgents set +# additionalAgentsOverride to `true`. +additionalClouds: {} +# remote-cloud-1: +# kubernetesURL: https://api.remote-cloud.com +# additionalAgentsOverride: true +# additionalAgents: +# maven-2: +# podName: maven-2 +# customJenkinsLabels: maven +# # An example of overriding the jnlp container +# # sideContainerName: jnlp +# image: +# repository: jenkins/jnlp-agent-maven +# tag: latest +# namespace: my-other-maven-namespace +# remote-cloud-2: +# kubernetesURL: https://api.remote-cloud.com + +persistence: + # -- Enable the use of a Jenkins PVC + enabled: true + + # A manually managed Persistent Volume and Claim + # Requires persistence.enabled: true + # If defined, PVC must be created manually before volume will be bound + # -- Provide the name of a PVC + existingClaim: + + # jenkins data Persistent Volume Storage Class + # If defined, storageClassName: + # If set to "-", storageClassName: "", which disables dynamic provisioning + # If undefined (the default) or set to null, no storageClassName spec is + # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS & OpenStack) + # -- Storage class for the PVC + storageClass: + # -- Annotations for the PVC + annotations: {} + # -- Labels for the PVC + labels: {} + # -- The PVC access mode + accessMode: "ReadWriteOnce" + # -- The size of the PVC + size: "8Gi" + + # ref: https://kubernetes.io/docs/concepts/storage/volume-pvc-datasource/ + # -- Existing data source to clone PVC from + dataSource: {} + # name: PVC-NAME + # kind: PersistentVolumeClaim + + # -- SubPath for jenkins-home mount + subPath: + # -- Additional volumes + volumes: [] + # - name: nothing + # emptyDir: {} + + # -- Additional mounts + mounts: [] + # - mountPath: /var/nothing + # name: nothing + # readOnly: true + +networkPolicy: + # -- Enable the creation of NetworkPolicy resources + enabled: false + + # For Kubernetes v1.4, v1.5 and v1.6, use 'extensions/v1beta1' + # For Kubernetes v1.7, use 'networking.k8s.io/v1' + # -- NetworkPolicy ApiVersion + apiVersion: networking.k8s.io/v1 + # You can allow agents to connect from both within the cluster (from within specific/all namespaces) AND/OR from a given external IP range + internalAgents: + # -- Allow internal agents (from the same cluster) to connect to controller. Agent pods will be filtered based on PodLabels + allowed: true + # -- A map of labels (keys/values) that agent pods must have to be able to connect to controller + podLabels: {} + # -- A map of labels (keys/values) that agents namespaces must have to be able to connect to controller + namespaceLabels: {} + # project: myproject + externalAgents: + # -- The IP range from which external agents are allowed to connect to controller, i.e., 172.17.0.0/16 + ipCIDR: + # -- A list of IP sub-ranges to be excluded from the allowlisted IP range + except: [] + # - 172.17.1.0/24 + +## Install Default RBAC roles and bindings +rbac: + # -- Whether RBAC resources are created + create: true + # -- Whether the Jenkins service account should be able to read Kubernetes secrets + readSecrets: false + +serviceAccount: + # -- Configures if a ServiceAccount with this name should be created + create: true + + # The name of the ServiceAccount is autogenerated by default + # -- The name of the ServiceAccount to be used by access-controlled resources + name: + # -- Configures annotations for the ServiceAccount + annotations: {} + # -- Configures extra labels for the ServiceAccount + extraLabels: {} + # -- Controller ServiceAccount image pull secret + imagePullSecretName: + + +serviceAccountAgent: + # -- Configures if an agent ServiceAccount should be created + create: false + + # If not set and create is true, a name is generated using the fullname template + # -- The name of the agent ServiceAccount to be used by access-controlled resources + name: + # -- Configures annotations for the agent ServiceAccount + annotations: {} + # -- Configures extra labels for the agent ServiceAccount + extraLabels: {} + # -- Agent ServiceAccount image pull secret + imagePullSecretName: + +# -- Checks if any deprecated values are used +checkDeprecation: true + +awsSecurityGroupPolicies: + enabled: false + policies: + - name: "" + securityGroupIds: [] + podSelector: {} + +# Here you can configure unit tests values when executing the helm unittest in the CONTRIBUTING.md +helmtest: + # A testing framework for bash + bats: + # Bash Automated Testing System (BATS) + image: + # -- Registry of the image used to test the framework + registry: "docker.io" + # -- Repository of the image used to test the framework + repository: "bats/bats" + # -- Tag of the image to test the framework + tag: "1.11.0" diff --git a/charts/kubecost/cost-analyzer/2.3.3/Chart.yaml b/charts/kubecost/cost-analyzer/2.3.3/Chart.yaml index 8b1638c46..995e29de9 100644 --- a/charts/kubecost/cost-analyzer/2.3.3/Chart.yaml +++ b/charts/kubecost/cost-analyzer/2.3.3/Chart.yaml @@ -4,7 +4,6 @@ annotations: url: https://www.kubecost.com catalog.cattle.io/certified: partner catalog.cattle.io/display-name: Kubecost - catalog.cattle.io/featured: "1" catalog.cattle.io/release-name: cost-analyzer apiVersion: v2 appVersion: 2.3.3 diff --git a/charts/kubecost/cost-analyzer/2.3.4/Chart.yaml b/charts/kubecost/cost-analyzer/2.3.4/Chart.yaml new file mode 100644 index 000000000..ec37fb92d --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/Chart.yaml @@ -0,0 +1,14 @@ +annotations: + artifacthub.io/links: | + - name: Homepage + url: https://www.kubecost.com + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: Kubecost + catalog.cattle.io/featured: "1" + catalog.cattle.io/release-name: cost-analyzer +apiVersion: v2 +appVersion: 2.3.4 +description: Kubecost Helm chart - monitor your cloud costs! +icon: file://assets/icons/cost-analyzer.png +name: cost-analyzer +version: 2.3.4 diff --git a/charts/kubecost/cost-analyzer/2.3.4/README.md b/charts/kubecost/cost-analyzer/2.3.4/README.md new file mode 100644 index 000000000..72da48c29 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/README.md @@ -0,0 +1,116 @@ +# Kubecost Helm chart + +This is the official Helm chart for [Kubecost](https://www.kubecost.com/), an enterprise-grade application to monitor and manage Kubernetes spend. Please see the [website](https://www.kubecost.com/) for more details on what Kubecost can do for you and the official documentation [here](https://docs.kubecost.com/), or contact [team@kubecost.com](mailto:team@kubecost.com) for assistance. + +To install via Helm, run the following command. + +```sh +helm upgrade --install kubecost -n kubecost --create-namespace \ + --repo https://kubecost.github.io/cost-analyzer/ cost-analyzer \ + --set kubecostToken="aGVsbUBrdWJlY29zdC5jb20=xm343yadf98" +``` + +Alternatively, add the Helm repository first and scan for updates. + +```sh +helm repo add kubecost https://kubecost.github.io/cost-analyzer/ +helm repo update +``` + +Next, install the chart. + +```sh +helm install kubecost kubecost/cost-analyzer -n kubecost --create-namespace \ + --set kubecostToken="aGVsbUBrdWJlY29zdC5jb20=xm343yadf98" +``` + +While Helm is the [recommended install path](http://kubecost.com/install) for Kubecost especially in production, Kubecost can alternatively be deployed with a single-file manifest using the following command. Keep in mind when choosing this method, Kubecost will be installed from a development branch and may include unreleased changes. + +```sh +kubectl apply -f https://raw.githubusercontent.com/kubecost/cost-analyzer-helm-chart/develop/kubecost.yaml +``` + +The following table lists commonly used configuration parameters for the Kubecost Helm chart and their default values. Please see the [values file](values.yaml) for the complete set of definable values. + +| Parameter | Description | Default | +|------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------| +| `global.prometheus.enabled` | If false, use an existing Prometheus install. [More info](http://docs.kubecost.com/custom-prom). | `true` | +| `prometheus.server.persistentVolume.enabled` | If true, Prometheus server will create a Persistent Volume Claim. | `true` | +| `prometheus.server.persistentVolume.size` | Prometheus server data Persistent Volume size. Default set to retain ~6000 samples per second for 15 days. | `32Gi` | +| `prometheus.server.retention` | Determines when to remove old data. | `15d` | +| `prometheus.server.resources` | Prometheus server resource requests and limits. | `{}` | +| `prometheus.nodeExporter.resources` | Node exporter resource requests and limits. | `{}` | +| `prometheus.nodeExporter.enabled` `prometheus.serviceAccounts.nodeExporter.create` | If false, do not crate NodeExporter daemonset. | `true` | +| `prometheus.alertmanager.persistentVolume.enabled` | If true, Alertmanager will create a Persistent Volume Claim. | `true` | +| `prometheus.pushgateway.persistentVolume.enabled` | If true, Prometheus Pushgateway will create a Persistent Volume Claim. | `true` | +| `persistentVolume.enabled` | If true, Kubecost will create a Persistent Volume Claim for product config data. | `true` | +| `persistentVolume.size` | Define PVC size for cost-analyzer | `32.0Gi` | +| `persistentVolume.dbSize` | Define PVC size for cost-analyzer's flat file database | `32.0Gi` | +| `ingress.enabled` | If true, Ingress will be created | `false` | +| `ingress.annotations` | Ingress annotations | `{}` | +| `ingress.className` | Ingress class name | `{}` | +| `ingress.paths` | Ingress paths | `["/"]` | +| `ingress.hosts` | Ingress hostnames | `[cost-analyzer.local]` | +| `ingress.tls` | Ingress TLS configuration (YAML) | `[]` | +| `networkPolicy.enabled` | If true, create a NetworkPolicy to deny egress | `false` | +| `networkPolicy.costAnalyzer.enabled` | If true, create a newtork policy for cost-analzyer | `false` | +| `networkPolicy.costAnalyzer.annotations` | Annotations to be added to the network policy | `{}` | +| `networkPolicy.costAnalyzer.additionalLabels` | Additional labels to be added to the network policy | `{}` | +| `networkPolicy.costAnalyzer.ingressRules` | A list of network policy ingress rules | `null` | +| `networkPolicy.costAnalyzer.egressRules` | A list of network policy egress rules | `null` | +| `networkCosts.enabled` | If true, collect network allocation metrics [More info](http://docs.kubecost.com/network-allocation) | `false` | +| `networkCosts.podMonitor.enabled` | If true, a [PodMonitor](https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#podmonitor) for the network-cost daemonset is created | `false` | +| `serviceMonitor.enabled` | Set this to `true` to create ServiceMonitor for Prometheus operator | `false` | +| `serviceMonitor.additionalLabels` | Additional labels that can be used so ServiceMonitor will be discovered by Prometheus | `{}` | +| `serviceMonitor.relabelings` | Sets Prometheus metric_relabel_configs on the scrape job | `[]` | +| `serviceMonitor.metricRelabelings` | Sets Prometheus relabel_configs on the scrape job | `[]` | +| `prometheusRule.enabled` | Set this to `true` to create PrometheusRule for Prometheus operator | `false` | +| `prometheusRule.additionalLabels` | Additional labels that can be used so PrometheusRule will be discovered by Prometheus | `{}` | +| `grafana.resources` | Grafana resource requests and limits. | `{}` | +| `grafana.serviceAccount.create` | If true, create a Service Account for Grafana. | `true` | +| `grafana.serviceAccount.name` | Grafana Service Account name. | `{}` | +| `grafana.sidecar.datasources.defaultDatasourceEnabled` | Set this to `false` to disable creation of Prometheus datasource in Grafana | `true` | +| `serviceAccount.create` | Set this to `false` if you want to create the service account `kubecost-cost-analyzer` on your own | `true` | +| `tolerations` | node taints to tolerate | `[]` | +| `affinity` | pod affinity | `{}` | +| `kubecostProductConfigs.productKey.mountPath` | Use instead of `kubecostProductConfigs.productKey.secretname` to declare the path at which the product key file is mounted (eg. by a secrets provisioner) | `N/A` | +| `kubecostFrontend.api.fqdn` | Customize the upstream api FQDN | `computed in terms of the service name and namespace` | +| `kubecostFrontend.model.fqdn` | Customize the upstream model FQDN | `computed in terms of the service name and namespace` | +| `clusterController.fqdn` | Customize the upstream cluster controller FQDN | `computed in terms of the service name and namespace` | +| `global.grafana.fqdn` | Customize the upstream grafana FQDN | `computed in terms of the release name and namespace` | + +## Adjusting Log Output + +The log output can be customized during deployment by using the `LOG_LEVEL` and/or `LOG_FORMAT` environment variables. + +### Adjusting Log Level + +Adjusting the log level increases or decreases the level of verbosity written to the logs. To set the log level to `trace`, the following flag can be added to the `helm` command. + +```sh +--set 'kubecostModel.extraEnv[0].name=LOG_LEVEL,kubecostModel.extraEnv[0].value=trace' +``` + +### Adjusting Log Format + +Adjusting the log format changes the format in which the logs are output making it easier for log aggregators to parse and display logged messages. The `LOG_FORMAT` environment variable accepts the values `JSON`, for a structured output, and `pretty` for a nice, human-readable output. + +| Value | Output | +|--------|----------------------------------------------------------------------------------------------------------------------------| +| `JSON` | `{"level":"info","time":"2006-01-02T15:04:05.999999999Z07:00","message":"Starting cost-model (git commit \"1.91.0-rc.0\")"}` | +| `pretty` | `2006-01-02T15:04:05.999999999Z07:00 INF Starting cost-model (git commit "1.91.0-rc.0")` | + +## Testing +To perform local testing do next: +- install locally [kind](https://github.com/kubernetes-sigs/kind) according to documentation. +- install locally [ct](https://github.com/helm/chart-testing) according to documentation. +- create local cluster using `kind` \ +use image version from https://github.com/kubernetes-sigs/kind/releases e.g. `kindest/node:v1.25.11@sha256:227fa11ce74ea76a0474eeefb84cb75d8dad1b08638371ecf0e86259b35be0c8` +```shell +kind create cluster --image kindest/node:v1.25.11@sha256:227fa11ce74ea76a0474eeefb84cb75d8dad1b08638371ecf0e86259b35be0c8 +``` +- perform ct execution +```shell +ct install --chart-dirs="." --charts="." +``` + diff --git a/charts/kubecost/cost-analyzer/2.3.4/app-readme.md b/charts/kubecost/cost-analyzer/2.3.4/app-readme.md new file mode 100644 index 000000000..90fd50607 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/app-readme.md @@ -0,0 +1,25 @@ +# Kubecost + +[Kubecost](https://kubecost.com/) is an open-source Kubernetes cost monitoring solution. + +Kubecost gives teams visibility into current and historical Kubernetes spend and resource allocation. These models provide cost transparency in Kubernetes environments that support multiple applications, teams, departments, etc. + +To see more on the functionality of the full Kubecost product, please visit the [features page](https://kubecost.com/#features) on our website. + +Here is a summary of features enabled by this cost model: + +- Real-time cost allocation by Kubernetes service, deployment, namespace, label, statefulset, daemonset, pod, and container +- Dynamic asset pricing enabled by integrations with AWS, Azure, and GCP billing APIs +- Supports on-prem k8s clusters with custom pricing sheets +- Allocation for in-cluster resources like CPU, GPU, memory, and persistent volumes. +- Allocation for AWS & GCP out-of-cluster resources like RDS instances and S3 buckets with key (optional) +- Easily export pricing data to Prometheus with /metrics endpoint ([learn more](https://github.com/kubecost/cost-model/blob/develop/PROMETHEUS.md)) +- Free and open source distribution (Apache2 license) + +## Requirements + +- Kubernetes 1.8+ +- kube-state-metrics +- Grafana +- Prometheus +- Node Exporter diff --git a/charts/kubecost/cost-analyzer/2.3.4/ci/aggregator-values.yaml b/charts/kubecost/cost-analyzer/2.3.4/ci/aggregator-values.yaml new file mode 100644 index 000000000..523b9e81b --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/ci/aggregator-values.yaml @@ -0,0 +1,17 @@ +kubecostAggregator: + enabled: true + cloudCost: + enabled: true + aggregatorDbStorage: + storageRequest: 10Gi +kubecostModel: + federatedStorageConfigSecret: federated-store +kubecostProductConfigs: + cloudIntegrationSecret: cloud-integration + clusterName: CLUSTER_NAME +prometheus: + server: + global: + external_labels: + # cluster_id should be unique for all clusters and the same value as .kubecostProductConfigs.clusterName + cluster_id: CLUSTER_NAME diff --git a/charts/kubecost/cost-analyzer/2.3.4/ci/federatedetl-primary-netcosts-values.yaml b/charts/kubecost/cost-analyzer/2.3.4/ci/federatedetl-primary-netcosts-values.yaml new file mode 100644 index 000000000..78ad05725 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/ci/federatedetl-primary-netcosts-values.yaml @@ -0,0 +1,35 @@ +kubecostProductConfigs: + clusterName: CLUSTER_NAME + # cloudIntegrationSecret: cloud-integration +federatedETL: + useExistingS3Config: false + federatedCluster: true +kubecostModel: + containerStatsEnabled: true + federatedStorageConfigSecret: federated-store +serviceAccount: # this example uses AWS IRSA, which creates a service account with rights to the s3 bucket. If using keys+secrets in the federated-store, set create: true + create: true +global: + prometheus: + enabled: true + # fqdn: http://prometheus-operated.monitoring:9090 + grafana: # prometheus metrics will be local cluster only, disable grafana to save resources + enabled: false + proxy: false +prometheus: + nodeExporter: + enabled: false + server: + global: + external_labels: + # cluster_id should be unique for all clusters and the same value as .kubecostProductConfigs.clusterName + cluster_id: CLUSTER_NAME +networkCosts: + # optional, see: https://docs.kubecost.com/install-and-configure/advanced-configuration/network-costs-configuration + enabled: true + config: + services: + # set the appropriate cloud provider to true + amazon-web-services: true + # google-cloud-services: true + # azure-cloud-services: true diff --git a/charts/kubecost/cost-analyzer/2.3.4/ci/statefulsets-cc.yaml b/charts/kubecost/cost-analyzer/2.3.4/ci/statefulsets-cc.yaml new file mode 100644 index 000000000..626a0c2e5 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/ci/statefulsets-cc.yaml @@ -0,0 +1,46 @@ +### This test is to verify that Kubecost aggregator is deployed as a StatefulSet, +### cluster controller is installed, and the various Prometheus components are installed. +global: + podAnnotations: + kubecost.io/test1: value1 + kubecost.io/test2: value2 + additionalLabels: + kubecosttest1: value1 + kubecosttest2: value2 + prometheus: + enabled: true + # fqdn: http://prometheus-operated.monitoring:9090 + grafana: # prometheus metrics will be local cluster only, disable grafana to save resources + enabled: false + proxy: false +kubecostProductConfigs: + clusterName: CLUSTER_NAME +kubecostAggregator: + deployMethod: statefulset +kubecostModel: + federatedStorageConfigSecret: federated-store +clusterController: + enabled: true + actionConfigs: + clusterTurndown: + - name: my-schedule2 + start: "2034-02-09T00:00:00Z" + end: "2034-02-09T01:00:00Z" + repeat: none +prometheus: + nodeExporter: + enabled: true + alertmanager: + enabled: true + configmapReload: + prometheus: + enabled: true + pushgateway: + enabled: true + server: + statefulSet: + enabled: true + global: + external_labels: + # cluster_id should be unique for all clusters and the same value as .kubecostProductConfigs.clusterName + cluster_id: CLUSTER_NAME \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/crds/cluster-turndown-crd.yaml b/charts/kubecost/cost-analyzer/2.3.4/crds/cluster-turndown-crd.yaml new file mode 100644 index 000000000..8c87644cc --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/crds/cluster-turndown-crd.yaml @@ -0,0 +1,78 @@ +# TurndownSchedule Custom Resource Definition for persistence +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: turndownschedules.kubecost.com +spec: + group: kubecost.com + names: + kind: TurndownSchedule + singular: turndownschedule + plural: turndownschedules + shortNames: + - td + - tds + scope: Cluster + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + start: + type: string + format: date-time + end: + type: string + format: date-time + repeat: + type: string + enum: [none, daily, weekly] + status: + type: object + properties: + state: + type: string + lastUpdated: + format: date-time + type: string + current: + type: string + scaleDownId: + type: string + nextScaleDownTime: + format: date-time + type: string + scaleDownMetadata: + additionalProperties: + type: string + type: object + scaleUpID: + type: string + nextScaleUpTime: + format: date-time + type: string + scaleUpMetadata: + additionalProperties: + type: string + type: object + additionalPrinterColumns: + - name: State + type: string + description: The state of the turndownschedule + jsonPath: .status.state + - name: Next Turndown + type: string + description: The next turndown date-time + jsonPath: .status.nextScaleDownTime + - name: Next Turn Up + type: string + description: The next turn up date-time + jsonPath: .status.nextScaleUpTime diff --git a/charts/kubecost/cost-analyzer/2.3.4/custom-pricing.csv b/charts/kubecost/cost-analyzer/2.3.4/custom-pricing.csv new file mode 100644 index 000000000..c3e6d2367 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/custom-pricing.csv @@ -0,0 +1,7 @@ +EndTimestamp,InstanceID,Region,AssetClass,InstanceIDField,InstanceType,MarketPriceHourly,Version +2028-01-06 23:34:45 UTC,,us-east-2,node,metadata.name,g4dn.xlarge,5.55, +2028-01-06 23:34:45 UTC,,,node,metadata.name,R730-type1,1.35, +2028-01-06 23:34:45 UTC,,,pv,metadata.name,standard,0.44, +2028-01-06 23:34:45 UTC,a100,,gpu,gpu.nvidia.com/class,,0.75, +2028-01-06 23:34:45 UTC,RTX3090,,gpu,nvidia.com/gpu_type,,0.65, +2028-01-06 23:34:45 UTC,i-01045ab6d13179700,,,spec.providerID,,1.2, diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/README.md b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/README.md new file mode 100644 index 000000000..160316ab6 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/README.md @@ -0,0 +1,45 @@ +# Kubecost Grafana Dashboards + +## Overview + +Kubecost, by default, is bundled with a Grafana instance that already contains the dashboards in this folder. + +The dashboards in this repo are imported into Kubecost, unless disabled with + + +The same dashboards have template versions in [grafana-templates/](grafana-templates/) for those wanting to load the dashboards into an existing Grafana instance. + +## Caveats + +The primary purpose of the dashboards provided is to allow visibility into the metrics used by Kubecost to create the cost-model. + +The networkCosts-metrics dashboard requires the optional networkCosts daemonset to be [enabled](https://docs.kubecost.com/install-and-configure/advanced-configuration/network-costs-configuration). + +## Metrics Required + +`kubecost-container-stats` metrics: + +``` +container_cpu_usage_seconds_total +kube_pod_container_resource_requests +container_memory_working_set_bytes +container_cpu_cfs_throttled_periods_total +container_cpu_cfs_periods_total +``` + +`network-transfer-data` metrics: + +``` +kubecost_pod_network_ingress_bytes_total +kubecost_pod_network_egress_bytes_total +``` + +`disk-usage` metrics: +``` +container_fs_limit_bytes +container_fs_usage_bytes +``` + +## Additional Information + +Kubecost Grafana [Configuration Guide](https://docs.kubecost.com/install-and-configure/advanced-configuration/custom-grafana) \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/attached-disks.json b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/attached-disks.json new file mode 100644 index 000000000..49c8d6c1a --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/attached-disks.json @@ -0,0 +1,549 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 16, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.0.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(container_fs_limit_bytes{instance=~'$disk', device!=\"tmpfs\", id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance)", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}}/{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Disk Size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.0.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(container_fs_usage_bytes{instance=~'$disk',id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance) / sum(container_fs_limit_bytes{instance=~'$disk',device!=\"tmpfs\", id=\"/\", cluster_id=~'$cluster'}) by (cluster_id,instance)", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}}-{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Disk Utilization", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 9 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.0.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "1 - sum(container_fs_inodes_free{instance=~'$disk',id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance) / sum(container_fs_inodes_total{instance=~'$disk',id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}}/{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "iNode Utilization", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 9 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.0.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(container_fs_usage_bytes{instance=~'$disk',id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance)", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}}/{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Disk Usage", + "type": "timeseries" + } + ], + "schemaVersion": 39, + "tags": [ + "kubecost", + "cost", + "utilization", + "metrics" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "PBFA97CFB590B2093" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": true, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "definition": "label_values(cluster_id)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "cluster", + "options": [], + "query": { + "query": "label_values(cluster_id)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": true, + "text": "ip-192-168-147-146.us-east-2.compute.internal", + "value": "ip-192-168-147-146.us-east-2.compute.internal" + }, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "definition": "label_values(container_fs_limit_bytes{cluster_id=~\"$cluster\"}, instance)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "disk", + "options": [], + "query": { + "query": "label_values(container_fs_limit_bytes{cluster_id=~\"$cluster\"}, instance)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-7d", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Attached disk metrics", + "uid": "nBH7qBgMk", + "version": 7, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/cluster-metrics.json b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/cluster-metrics.json new file mode 100644 index 000000000..253556000 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/cluster-metrics.json @@ -0,0 +1,1683 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Cost metrics from the Kubecost product", + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 7, + "iteration": 1558062099204, + "links": [], + "panels": [ + { + "content": "Deprecated - It is not expected to match Kubecost UI/API.", + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 27, + "links": [], + "mode": "markdown", + "title": "", + "transparent": true, + "type": "text" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "${datasource}", + "decimals": 2, + "description": "Monthly run rate of CPU + GPU costs based on currently provisioned resources.", + "format": "currencyUSD", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 2 + }, + "hideTimeOverride": true, + "id": 2, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": true, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100) +\n avg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n)", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "thresholds": "", + "timeFrom": "15m", + "timeShift": null, + "title": "CPU Cost", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "${datasource}", + "decimals": 2, + "description": "Monthly run rate of memory costs based on currently provisioned expenses.", + "format": "currencyUSD", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 6, + "y": 2 + }, + "hideTimeOverride": true, + "id": 3, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n)", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "thresholds": "", + "timeFrom": "15m", + "timeShift": null, + "title": "Memory Cost", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "${datasource}", + "decimals": 2, + "description": "Monthly run rate of attached storage and PV costs based on currently provisioned resources.", + "format": "currencyUSD", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 12, + "y": 2 + }, + "hideTimeOverride": true, + "id": 4, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024 / 1024 / 1024) \n+\nsum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "thresholds": "", + "timeFrom": "15m", + "timeShift": null, + "title": "Storage Cost", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "${datasource}", + "decimals": 2, + "description": "Sum of compute, memory, storage and network costs.", + "format": "currencyUSD", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 2 + }, + "hideTimeOverride": true, + "id": 11, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "# Compute\nsum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100) +\n avg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n) +\n\n\n# Memory\nsum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n) +\n\n# Storage \n\nsum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024 / 1024 / 1024) \n+\nsum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "thresholds": "", + "timeFrom": "15m", + "timeShift": null, + "title": "Total Cost", + "type": "singlestat", + "valueFontSize": "120%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#c15c17" + ], + "datasource": "${datasource}", + "decimals": 2, + "description": "Current CPU use from applications divided by allocatable CPUs", + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 0, + "y": 5 + }, + "height": "180px", + "hideTimeOverride": true, + "id": 13, + "interval": null, + "isNew": true, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "(\n sum(\n count(irate(container_cpu_usage_seconds_total{id=\"/\"}[10m])) by (instance)\n * on (instance) \n sum(irate(container_cpu_usage_seconds_total{id=\"/\"}[10m])) by (instance)\n ) \n / \n (sum (kube_node_status_allocatable{resource=\"cpu\", unit=\"core\"}))\n) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "thresholds": "30, 80", + "timeFrom": "", + "title": "CPU Utilization", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#c15c17" + ], + "datasource": "${datasource}", + "decimals": 2, + "description": "Current CPU reservation requests from applications vs allocatable CPU", + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 3, + "y": 5 + }, + "height": "180px", + "id": 15, + "interval": null, + "isNew": true, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "SUM(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\"}) / SUM(kube_node_status_allocatable{resource=\"cpu\", unit=\"core\"}) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "thresholds": "30, 80", + "title": "CPU Requests", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#c15c17" + ], + "datasource": "${datasource}", + "description": "Current RAM use vs RAM available", + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 6, + "y": 5 + }, + "height": "180px", + "hideTimeOverride": true, + "id": 17, + "interval": null, + "isNew": true, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "SUM(container_memory_usage_bytes{namespace!=\"\"}) / SUM(kube_node_status_allocatable{resource=\"memory\", unit=\"byte\"}) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + }, + { + "expr": "", + "format": "time_series", + "intervalFactor": 1, + "refId": "B" + } + ], + "thresholds": "30,80", + "timeFrom": "", + "title": "RAM Utilization", + "transparent": false, + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#c15c17" + ], + "datasource": "${datasource}", + "description": "Current RAM requests vs RAM available", + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 9, + "y": 5 + }, + "height": "180px", + "id": 19, + "interval": null, + "isNew": true, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "(\n sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\"})\n /\n sum(kube_node_status_allocatable{resource=\"memory\", unit=\"byte\"})\n) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "thresholds": "30,80", + "title": "RAM Requests", + "transparent": false, + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#c15c17" + ], + "datasource": "${datasource}", + "decimals": 2, + "description": "This gauge shows the current standard storage use, including cluster storage, vs storage available", + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 5 + }, + "height": "180px", + "hideTimeOverride": true, + "id": 21, + "interval": null, + "isNew": true, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum (\n sum(kube_persistentvolumeclaim_info) by (persistentvolumeclaim, namespace)\n + on (persistentvolumeclaim, namespace)\n sum(pod_pvc_allocation) by (persistentvolumeclaim, namespace) or up * 0\n + sum(container_fs_usage_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\"})\n) /\nsum (\n sum(kube_persistentvolumeclaim_info) by (persistentvolumeclaim, namespace)\n + on (persistentvolumeclaim, namespace)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) or up * 0\n + sum(container_fs_limit_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\"})\n) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "thresholds": "30, 80", + "timeFrom": "", + "title": "Storage Utilization", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "description": "Monthly run rate of CPU + GPU costs", + "fill": 1, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 9 + }, + "id": 6, + "interval": "1m", + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 +\n avg(node_gpu_hourly_cost) by (node) * 730\n)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "compute cost", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Compute Cost", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "currencyUSD", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "description": "Monthly run rate of memory costs", + "fill": 1, + "gridPos": { + "h": 7, + "w": 6, + "x": 6, + "y": 9 + }, + "id": 9, + "interval": "1m", + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730\n)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "memory cost", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Memory Cost", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "currencyUSD", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "description": "Monthly run rate of attached disk + PV storage costs", + "fill": 1, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 9 + }, + "id": 10, + "interval": "1m", + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(\n avg(avg_over_time(pv_hourly_cost[$timeRange] offset 1m)) by (persistentvolume) * 730 \n * avg(avg_over_time(kube_persistentvolume_capacity_bytes[$timeRange] offset 1m)) by (persistentvolume) / 1024 / 1024 / 1024\n) +\nsum(avg(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "storage cost", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Storage Cost", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "currencyUSD", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "description": "Sum of compute, memory, and storage costs", + "fill": 1, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 9 + }, + "id": 22, + "interval": "1m", + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "# Compute\nsum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100) +\n avg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n) +\n\n\n# Memory\nsum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n) +\n\n# Storage \n\nsum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024 / 1024 / 1024) \n+\nsum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "total cost", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Total Cost", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "currencyUSD", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "columns": [], + "datasource": "${datasource}", + "description": "Cost of by resource class of currently provisioned nodes", + "fontSize": "100%", + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 8, + "links": [], + "pageSize": null, + "scroll": true, + "showHeader": true, + "sort": { + "col": 4, + "desc": false + }, + "styles": [ + { + "alias": "", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Time", + "thresholds": [], + "type": "hidden", + "unit": "short" + }, + { + "alias": "Compute Cost", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value", + "thresholds": [], + "type": "number", + "unit": "short" + }, + { + "alias": "CPU Cost", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #A", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "Mem Cost", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #B", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "Total", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #C", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "instance", + "thresholds": [], + "type": "hidden", + "unit": "short" + }, + { + "alias": "GPU", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #D", + "thresholds": [], + "type": "number", + "unit": "short" + }, + { + "alias": "", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "targets": [ + { + "expr": "avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost or up * 0) by (node) * 730 * (1-$useDiscount/100)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "legendFormat": "", + "refId": "A" + }, + { + "expr": "avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "legendFormat": "", + "refId": "B" + }, + { + "expr": "avg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "D" + }, + { + "expr": "# CPU \navg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost or up * 0) by (node) * 730 * (1-$useDiscount/100) +\n# GPU\navg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100) +\n# Memory\navg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "C" + } + ], + "title": "Cost by node", + "transform": "table", + "type": "table" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "description": "Monthly run rate of attached disk + PV storage costs based on currently provisioned resources.", + "fill": 1, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 25, + "interval": "1m", + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "sum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 +\n avg(node_gpu_hourly_cost) by (node) * 730\n)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "cpu", + "refId": "B" + }, + { + "expr": "sum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730\n)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "memory", + "refId": "A" + }, + { + "expr": "sum(\n avg(avg_over_time(pv_hourly_cost[$timeRange] offset 1m)) by (persistentvolume) * 730 \n * avg(avg_over_time(kube_persistentvolume_capacity_bytes[$timeRange] offset 1m)) by (persistentvolume) / 1024 / 1024 / 1024\n) +\nsum(avg(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "storage", + "refId": "C" + }, + { + "expr": "SUM(rate(node_network_transmit_bytes_total{device=\"eth0\"}[60m]) / 1024 / 1024 / 1024 ) * (60 * 60 * 24 * 30) * $percentEgress * $egressCost ", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "network", + "refId": "D" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Cost by Resource", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "currencyUSD", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": false, + "schemaVersion": 16, + "style": "dark", + "tags": [ + "kubecost", + "cost", + "utilization", + "metrics" + ], + "templating": { + "list": [ + { + "auto": true, + "auto_count": 1, + "auto_min": "1m", + "current": { + "text": "auto", + "value": "$__auto_interval_timeRange" + }, + "hide": 2, + "label": null, + "name": "timeRange", + "options": [ + { + "selected": true, + "text": "auto", + "value": "$__auto_interval_timeRange" + }, + { + "selected": false, + "text": "1h", + "value": "1h" + }, + { + "selected": false, + "text": "6h", + "value": "6h" + }, + { + "selected": false, + "text": "12h", + "value": "12h" + }, + { + "selected": false, + "text": "1d", + "value": "1d" + }, + { + "selected": false, + "text": "7d", + "value": "7d" + }, + { + "selected": false, + "text": "14d", + "value": "14d" + }, + { + "selected": false, + "text": "30d", + "value": "30d" + }, + { + "selected": false, + "text": "90d", + "value": "90d" + } + ], + "query": "1h,6h,12h,1d,7d,14d,30d,90d", + "refresh": 2, + "skipUrlSync": false, + "type": "interval" + }, + { + "current": { + "text": ".04", + "value": ".04" + }, + "hide": 2, + "label": "Cost per Gb hour for attached disks", + "name": "localStorageGBCost", + "options": [ + { + "selected": true, + "text": ".04", + "value": ".04" + } + ], + "query": ".04", + "skipUrlSync": false, + "type": "constant" + }, + { + "current": { + "tags": [], + "text": "0", + "value": "0" + }, + "hide": 0, + "label": "Sustained Use Discount %", + "name": "useDiscount", + "options": [ + { + "selected": true, + "text": "0", + "value": "0" + } + ], + "query": "0", + "skipUrlSync": false, + "type": "constant" + }, + { + "current": { + "text": ".1", + "value": ".1" + }, + "hide": 2, + "label": null, + "name": "percentEgress", + "options": [ + { + "selected": true, + "text": ".1", + "value": ".1" + } + ], + "query": ".1", + "skipUrlSync": false, + "type": "constant" + }, + { + "current": { + "text": ".12", + "value": ".12" + }, + "hide": 2, + "label": null, + "name": "egressCost", + "options": [ + { + "selected": true, + "text": ".12", + "value": ".12" + } + ], + "query": ".12", + "skipUrlSync": false, + "type": "constant" + }, + { + "current": { + "selected": true, + "text": "default-kubecost", + "value": "default-kubecost" + }, + "error": null, + "hide": 0, + "includeAll": false, + "label": null, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time": { + "from": "now-7d", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Deprecated - Kubecost cluster metrics", + "uid": "JOUdHGZZz", + "version": 20 +} diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/cluster-utilization.json b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/cluster-utilization.json new file mode 100644 index 000000000..8a17f26c0 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/cluster-utilization.json @@ -0,0 +1,3196 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "A dashboard to help manage Kubernetes cluster costs and resources", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": 6873, + "graphTooltip": 0, + "id": 10, + "iteration": 1645112913364, + "links": [], + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 86, + "links": [], + "options": { + "content": "Deprecated - It is not expected to match Kubecost UI/API. This dashboard shows monthly cost estimates for the cluster, based on **current** CPU, RAM and storage provisioned.", + "mode": "markdown" + }, + "pluginVersion": "8.3.2", + "transparent": true, + "type": "text" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 2 + }, + "hideTimeOverride": true, + "id": 75, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P0C970EB638C812D0" + }, + "exemplar": false, + "expr": "sum(\n (\n (\n sum(kube_node_status_capacity_cpu_cores) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible=\"true\"}) by (node)\n ) * $costpcpu\n )\n or\n (\n (\n sum(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible!=\"true\"}) by (node)\n ) * ($costcpu - ($costcpu / 100 * $costDiscount))\n )\n) ", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "CPU Cost", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 2 + }, + "hideTimeOverride": true, + "id": 77, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P0C970EB638C812D0" + }, + "exemplar": false, + "expr": "sum(\n (\n (\n sum(kube_node_status_capacity_memory_bytes) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible=\"true\"}) by (node)\n ) /1024/1024/1024 * $costpram\n )\n or\n (\n (\n sum(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible!=\"true\"}) by (node)\n ) /1024/1024/1024 * ($costram - ($costram / 100 * $costDiscount))\n)\n) ", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "RAM Cost", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 2 + }, + "hideTimeOverride": true, + "id": 78, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P0C970EB638C812D0" + }, + "exemplar": false, + "expr": "sum (\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) or up * 0\n) / 1024 / 1024 /1024 * $costStorageSSD\n\n+\n\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) or up * 0\n) / 1024 / 1024 /1024 * $costStorageStandard\n\n+ \n\nsum(container_fs_limit_bytes{id=\"/\"}) / 1024 / 1024 / 1024 * 1.03 * $costStorageStandard", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "Storage Cost (Cluster and PVC)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Represents a near worst-case approximation of network costs.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 2 + }, + "hideTimeOverride": true, + "id": 129, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P0C970EB638C812D0" + }, + "exemplar": false, + "expr": "SUM(rate(node_network_transmit_bytes_total{device=\"eth0\"}[60m]) / 1024 / 1024 / 1024 ) * (60 * 60 * 24 * 30) * $costEgress", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "Network Egress Cost", + "type": "stat" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Current CPU use from applications divided by allocatable CPUs", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(245, 54, 54, 0.9)", + "value": null + }, + { + "color": "rgba(50, 172, 45, 0.97)", + "value": 30 + }, + { + "color": "#c15c17", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 0, + "y": 6 + }, + "hideTimeOverride": true, + "id": 82, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "expr": "(\n sum(\n count(irate(container_cpu_usage_seconds_total{id=\"/\"}[10m])) by (instance)\n * on (instance) \n sum(irate(container_cpu_usage_seconds_total{id=\"/\"}[10m])) by (instance)\n ) \n / \n (sum (kube_node_status_allocatable{resource=\"cpu\", unit=\"core\"}))\n) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "timeFrom": "", + "title": "CPU Utilization", + "type": "gauge" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Current CPU reservation requests from applications vs allocatable CPU", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(245, 54, 54, 0.9)", + "value": null + }, + { + "color": "rgba(50, 172, 45, 0.97)", + "value": 30 + }, + { + "color": "#c15c17", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 3, + "y": 6 + }, + "id": 91, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "expr": "SUM(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\"}) / SUM(kube_node_status_allocatable{resource=\"cpu\", unit=\"core\"}) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "title": "CPU Requests", + "type": "gauge" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Current RAM use vs RAM available", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(245, 54, 54, 0.9)", + "value": null + }, + { + "color": "rgba(50, 172, 45, 0.97)", + "value": 30 + }, + { + "color": "#c15c17", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 6, + "y": 6 + }, + "hideTimeOverride": true, + "id": 80, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "expr": "SUM(container_memory_working_set_bytes{name!=\"POD\", container!=\"\", namespace!=\"\"}) / SUM(kube_node_status_allocatable{resource=\"memory\", unit=\"byte\"}) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + }, + { + "expr": "", + "format": "time_series", + "intervalFactor": 1, + "refId": "B" + } + ], + "timeFrom": "", + "title": "RAM Utilization", + "type": "gauge" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Current RAM requests vs RAM available", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(245, 54, 54, 0.9)", + "value": null + }, + { + "color": "rgba(50, 172, 45, 0.97)", + "value": 30 + }, + { + "color": "#c15c17", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 9, + "y": 6 + }, + "id": 92, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "expr": "(\n sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\"})\n /\n sum(kube_node_status_allocatable{resource=\"memory\", unit=\"byte\"})\n) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "title": "RAM Requests", + "type": "gauge" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "This gauge shows the current standard storage use, including cluster storage, vs storage available", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(245, 54, 54, 0.9)", + "value": null + }, + { + "color": "rgba(50, 172, 45, 0.97)", + "value": 30 + }, + { + "color": "#c15c17", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 12, + "y": 6 + }, + "hideTimeOverride": true, + "id": 95, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "expr": "sum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kubelet_volume_stats_used_bytes) by (persistentvolumeclaim, namespace) or up * 0\n + sum(container_fs_usage_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\"})\n) /\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) or up * 0\n + sum(container_fs_limit_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\"})\n) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "timeFrom": "", + "title": "Storage Utilization", + "type": "gauge" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "This gauge shows the current SSD use vs SSD available", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(245, 54, 54, 0.9)", + "value": null + }, + { + "color": "rgba(50, 172, 45, 0.97)", + "value": 30 + }, + { + "color": "#c15c17", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 15, + "y": 6 + }, + "hideTimeOverride": true, + "id": 96, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "expr": "sum (\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kubelet_volume_stats_used_bytes) by (persistentvolumeclaim, namespace)\n) /\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace)\n) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "timeFrom": "", + "title": "SSD Utilization", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Expected monthly cost given current CPU, memory storage, and network resource consumption", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 6 + }, + "hideTimeOverride": true, + "id": 93, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P0C970EB638C812D0" + }, + "exemplar": false, + "expr": "# CPU\nsum(\n (\n (\n sum(kube_node_status_capacity_cpu_cores) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible=\"true\"}) by (node)\n ) * $costpcpu\n )\n or\n (\n (\n sum(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible!=\"true\"}) by (node)\n ) * ($costcpu - ($costcpu / 100 * $costDiscount))\n )\n) \n\n+ \n\n# Storage\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) or up * 0\n) / 1024 / 1024 /1024 * $costStorageSSD\n\n+\n\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) or up * 0\n) / 1024 / 1024 /1024 * $costStorageStandard\n\n+ \n\nsum(container_fs_limit_bytes{id=\"/\"}) / 1024 / 1024 / 1024 * 1.03 * $costStorageStandard \n\n+\n\n# END STORAGE\n# RAM \nsum(\n (\n (\n sum(kube_node_status_capacity_memory_bytes) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible=\"true\"}) by (node)\n ) /1024/1024/1024 * $costpram\n )\n or\n (\n (\n sum(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible!=\"true\"}) by (node)\n ) /1024/1024/1024 * ($costram - ($costram / 100 * $costDiscount))\n)\n)\n\n+\n\n#Network \nSUM(rate(node_network_transmit_bytes_total{device=\"eth0\"}[60m]) / 1024 / 1024 / 1024 ) * (60 * 60 * 24 * 30) * $costEgress", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "Total Monthly Cost", + "type": "stat" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "description": "Expected monthly CPU, memory and storage costs given provisioned resources", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 10 + }, + "hiddenSeries": false, + "id": 120, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.3.2", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "# CPU\nsum(\n (\n (\n sum(kube_node_status_capacity_cpu_cores) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible=\"true\"}) by (node)\n ) * $costpcpu\n )\n or\n (\n (\n sum(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible!=\"true\"}) by (node)\n ) * ($costcpu - ($costcpu / 100 * $costDiscount))\n )\n) \n\n+ \n\n# Storage\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) or up * 0\n) / 1024 / 1024 /1024 * $costStorageSSD\n\n+\n\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) or up * 0\n) / 1024 / 1024 /1024 * $costStorageStandard\n\n+ \n\nsum(container_fs_limit_bytes{id=\"/\"}) / 1024 / 1024 / 1024 * 1.03 * $costStorageStandard \n\n+\n\n# END STORAGE\n# RAM \nsum(\n (\n (\n sum(kube_node_status_capacity_memory_bytes) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible=\"true\"}) by (node)\n ) /1024/1024/1024 * $costpram\n )\n or\n (\n (\n sum(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible!=\"true\"}) by (node)\n ) /1024/1024/1024 * ($costram - ($costram / 100 * $costDiscount))\n)\n) \n\n+\n\n#Network \nSUM(rate(node_network_transmit_bytes_total{device=\"eth0\"}[60m]) / 1024 / 1024 / 1024 ) * (60 * 60 * 24 * 30) * $costEgress", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "cluster cost", + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Total monthly cost", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "currencyUSD", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "columns": [ + { + "text": "Avg", + "value": "avg" + } + ], + "datasource": { + "uid": "${datasource}" + }, + "description": "Resources allocated to namespace based on container requests", + "fontSize": "100%", + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 10 + }, + "hideTimeOverride": false, + "id": 73, + "links": [], + "pageSize": 10, + "repeatDirection": "v", + "scroll": true, + "showHeader": true, + "sort": { + "col": 7, + "desc": true + }, + "styles": [ + { + "alias": "Namespace", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#c15c17" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "link": true, + "linkTooltip": "View namespace cost metrics", + "linkUrl": "d/at-cost-analysis-namespace2/namespace-cost-metrics?&var-namespace=$__cell", + "pattern": "namespace", + "thresholds": [ + "30", + "80" + ], + "type": "string", + "unit": "currencyUSD" + }, + { + "alias": "RAM", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "pattern": "Value #B", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "CPU", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #A", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Time", + "thresholds": [], + "type": "hidden", + "unit": "short" + }, + { + "alias": "PV Storage", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #C", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "Total", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #D", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "CPU Utilization", + "align": "auto", + "colorMode": "value", + "colors": [ + "#bf1b00", + "rgba(50, 172, 45, 0.97)", + "#ef843c" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #E", + "thresholds": [ + "30", + "80" + ], + "type": "number", + "unit": "percent" + }, + { + "alias": "RAM Utilization", + "align": "auto", + "colorMode": "value", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#ef843c" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #F", + "thresholds": [ + "30", + "80" + ], + "type": "number", + "unit": "percent" + } + ], + "targets": [ + { + "expr": "(\n sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", namespace!=\"\",namespace!=\"kube-system\",cloud_google_com_gke_preemptible!=\"true\"}*($costcpu - ($costcpu / 100 * $costDiscount))) by(namespace)\n or\n count(\n count(container_spec_cpu_shares{namespace!=\"\",namespace!=\"kube-system\"}) by(namespace)\n ) by(namespace) -1\n)\n\n+\n\n(\n sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", namespace!=\"\",namespace!=\"kube-system\",cloud_google_com_gke_preemptible=\"true\"}*$costpcpu) by(namespace)\n or\n count(\n count(container_spec_cpu_shares{namespace!=\"\",namespace!=\"kube-system\"}) by(namespace)\n ) by(namespace) -1\n)", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{ namespace }}", + "refId": "A" + }, + { + "expr": "(\n sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\",namespace!=\"kube-system\",cloud_google_com_gke_preemptible!=\"true\"} / 1024 / 1024 / 1024*($costram- ($costram / 100 * $costDiscount))) by (namespace) \n or\n count(\n count(container_spec_memory_limit_bytes{namespace!=\"\",namespace!=\"kube-system\"}) by(namespace)\n ) by(namespace) -1\n)\n\n+\n\n(\n sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\",namespace!=\"kube-system\",cloud_google_com_gke_preemptible=\"true\"} / 1024 / 1024 / 1024 * $costpram ) by (namespace) \n or\n count(\n count(container_spec_memory_limit_bytes{namespace!=\"\",namespace!=\"kube-system\"}) by(namespace)\n ) by(namespace) -1\n)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "legendFormat": "{{ namespace }}", + "refId": "B" + }, + { + "expr": "sum (\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) \n) by (namespace) / 1024 / 1024 /1024 * $costStorageSSD \n\nor\n\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) \n) by (namespace) / 1024 / 1024 /1024 * $costStorageStandard", + "format": "table", + "instant": true, + "intervalFactor": 1, + "legendFormat": "{{ namespace }}", + "refId": "C" + }, + { + "expr": "# CPU \n(\n sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", namespace!=\"\",namespace!=\"kube-system\",cloud_google_com_gke_preemptible!=\"true\"}*($costcpu - ($costcpu / 100 * $costDiscount))) by(namespace)\n or\n count(\n count(container_spec_cpu_shares{namespace!=\"\",namespace!=\"kube-system\"}) by(namespace)\n ) by(namespace) -1\n)\n\n+\n\n(\n sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", namespace!=\"\",namespace!=\"kube-system\",cloud_google_com_gke_preemptible=\"true\"}*$costpcpu) by(namespace)\n or\n count(\n count(container_spec_cpu_shares{namespace!=\"\",namespace!=\"kube-system\"}) by(namespace)\n ) by(namespace) -1\n)\n\n+\n\n#END CPU \n# Memory \n\n(\n sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\",namespace!=\"kube-system\",cloud_google_com_gke_preemptible!=\"true\"} / 1024 / 1024 / 1024*($costram- ($costram / 100 * $costDiscount))) by (namespace) \n or\n count(\n count(container_spec_memory_limit_bytes{namespace!=\"\",namespace!=\"kube-system\"}) by(namespace)\n ) by(namespace) -1\n)\n\n+\n\n(\n sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\",namespace!=\"kube-system\",cloud_google_com_gke_preemptible=\"true\"} / 1024 / 1024 / 1024 * $costpram ) by (namespace) \n or\n count(\n count(container_spec_memory_limit_bytes{namespace!=\"\",namespace!=\"kube-system\"}) by(namespace)\n ) by(namespace) -1\n)\n\n+\n\n# PV storage\n\n(\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) \n) by (namespace) / 1024 / 1024 /1024 * $costStorageSSD \n\nor\n\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) \n) by (namespace) / 1024 / 1024 /1024 * $costStorageStandard \n)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "legendFormat": "Total", + "refId": "D" + } + ], + "timeFrom": "", + "title": "Namespace cost allocation", + "transform": "table", + "type": "table-old" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 18 + }, + "id": 108, + "panels": [], + "title": "CPU Metrics", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 19 + }, + "hiddenSeries": false, + "id": 116, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.3.2", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "SUM(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"})", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "capacity", + "refId": "A" + }, + { + "expr": "SUM(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\"})", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "requests", + "refId": "C" + }, + { + "expr": "SUM(irate(container_cpu_usage_seconds_total{id=\"/\"}[5m]))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "usage", + "refId": "B" + }, + { + "expr": "SUM(kube_pod_container_resource_limits{resource=\"cpu\", unit=\"core\"}) ", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "limits", + "refId": "D" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Cluster CPUs", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 1, + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 27 + }, + "hiddenSeries": false, + "id": 130, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.3.2", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "avg(irate(node_cpu_seconds_total{mode!=\"idle\"}[5m])) by (mode) * 100", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{mode}}", + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "CPU Mode", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percent", + "logBase": 1, + "show": true + }, + { + "format": "percent", + "logBase": 1, + "show": false + } + ], + "yaxis": { + "align": false + } + }, + { + "columns": [ + { + "text": "Avg", + "value": "avg" + } + ], + "datasource": { + "uid": "${datasource}" + }, + "description": "This table shows the comparison of CPU requests and usage by namespace", + "fontSize": "100%", + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 35 + }, + "hideTimeOverride": true, + "id": 104, + "links": [], + "pageSize": 8, + "repeatDirection": "v", + "scroll": true, + "showHeader": true, + "sort": { + "col": 1, + "desc": true + }, + "styles": [ + { + "alias": "CPU Requests", + "align": "auto", + "colors": [ + "#fceaca", + "#fce2de", + "rgba(245, 54, 54, 0.9)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #A", + "thresholds": [ + "" + ], + "type": "number", + "unit": "short" + }, + { + "alias": "Node", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "node", + "thresholds": [], + "type": "string", + "unit": "short" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Time", + "thresholds": [], + "type": "hidden", + "unit": "short" + }, + { + "alias": "CPU Requests", + "align": "auto", + "colorMode": "value", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#cffaff" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #B", + "thresholds": [ + "" + ], + "type": "number", + "unit": "short" + }, + { + "alias": "24h CPU Usage", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #C", + "thresholds": [ + "30" + ], + "type": "number", + "unit": "none" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "link": true, + "linkTooltip": "View namespace cost metrics", + "linkUrl": "d/at-cost-analysis-namespace2/namespace-cost-metrics?&var-namespace=$__cell", + "mappingType": 1, + "pattern": "namespace", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "targets": [ + { + "expr": "sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", namespace!=\"\"}) by (namespace) ", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "", + "refId": "A" + }, + { + "expr": "sum (rate (container_cpu_usage_seconds_total{image!=\"\",namespace!=\"\"}[24h])) by (namespace)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "legendFormat": "{{ namespace }}", + "refId": "C" + } + ], + "title": "CPU request utilization by namespace", + "transform": "table", + "type": "table-old" + }, + { + "columns": [ + { + "text": "Avg", + "value": "avg" + } + ], + "datasource": { + "uid": "${datasource}" + }, + "description": "This table shows the comparison of application CPU usage vs the capacity of the node (measured over last 60 minutes)", + "fontSize": "100%", + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 35 + }, + "hideTimeOverride": true, + "id": 90, + "links": [], + "pageSize": 8, + "repeatDirection": "v", + "scroll": true, + "showHeader": true, + "sort": { + "col": 2, + "desc": true + }, + "styles": [ + { + "alias": "CPU Request Utilization", + "align": "auto", + "colorMode": "value", + "colors": [ + "#ef843c", + "rgba(50, 172, 45, 0.97)", + "rgba(245, 54, 54, 0.9)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #A", + "thresholds": [ + ".30", + " .80" + ], + "type": "number", + "unit": "percentunit" + }, + { + "alias": "Node", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "node", + "thresholds": [], + "type": "string", + "unit": "short" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Time", + "thresholds": [], + "type": "hidden", + "unit": "short" + }, + { + "alias": "CPU Utilization", + "align": "auto", + "colorMode": "value", + "colors": [ + "#ef843c", + "rgba(50, 172, 45, 0.97)", + "rgba(245, 54, 54, 0.9)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #B", + "thresholds": [ + ".20", + " .80" + ], + "type": "number", + "unit": "percentunit" + }, + { + "alias": "24h Utilization ", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value", + "thresholds": [], + "type": "number", + "unit": "percentunit" + } + ], + "targets": [ + { + "expr": "SUM(\nSUM(rate(container_cpu_usage_seconds_total[24h])) by (pod_name)\n* on (pod_name) group_left (node) \nlabel_replace(\n avg(kube_pod_info{}),\n \"pod_name\", \n \"$1\", \n \"pod\", \n \"(.+)\"\n)\n) by (node) \n/ \nsum(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "B" + }, + { + "expr": "sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\"}) by (node) / sum(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "A" + } + ], + "title": "Cluster cost & utilization by node", + "transform": "table", + "type": "table-old" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 45 + }, + "id": 113, + "panels": [], + "title": "Memory Metrics", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 46 + }, + "id": 117, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "8.3.2", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "SUM(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"} / 1024 / 1024 / 1024)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "capacity", + "refId": "A" + }, + { + "expr": "SUM(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\"} / 1024 / 1024 / 1024)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "requests", + "refId": "C" + }, + { + "expr": "SUM(container_memory_usage_bytes{image!=\"\"} / 1024 / 1024 / 1024)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "usage", + "refId": "B" + }, + { + "expr": "SUM(kube_pod_container_resource_limits{resource=\"memory\", unit=\"byte\", namespace!=\"\"} / 1024 / 1024 / 1024)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "limits", + "refId": "D" + } + ], + "thresholds": [], + "title": "Cluster memory (GB)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decgbytes", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 54 + }, + "id": 131, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "8.3.2", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "1 - sum(node_memory_MemAvailable_bytes) by (node) / sum(node_memory_MemTotal_bytes) by (node)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "usage", + "refId": "A" + } + ], + "thresholds": [], + "title": "Cluster Memory Utilization", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percentunit", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "columns": [ + { + "text": "Avg", + "value": "avg" + } + ], + "datasource": { + "uid": "${datasource}" + }, + "description": "Comparison of memory requests and current usage by namespace", + "fontSize": "100%", + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 62 + }, + "hideTimeOverride": true, + "id": 109, + "links": [], + "pageSize": 7, + "repeatDirection": "v", + "scroll": true, + "showHeader": true, + "sort": { + "col": 1, + "desc": true + }, + "styles": [ + { + "alias": "Mem Requests (GB)", + "align": "auto", + "colors": [ + "#fceaca", + "#fce2de", + "rgba(245, 54, 54, 0.9)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #A", + "thresholds": [ + "" + ], + "type": "number", + "unit": "short" + }, + { + "alias": "Node", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "node", + "thresholds": [], + "type": "string", + "unit": "short" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Time", + "thresholds": [], + "type": "hidden", + "unit": "short" + }, + { + "alias": "CPU Requests", + "align": "auto", + "colorMode": "value", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#cffaff" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #B", + "thresholds": [ + "" + ], + "type": "number", + "unit": "short" + }, + { + "alias": "24h Mem Usage", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "#508642", + "#e5ac0e" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #C", + "thresholds": [ + ".30", + ".75" + ], + "type": "number", + "unit": "none" + }, + { + "alias": "Namespace", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "link": true, + "linkTooltip": "View namespace cost metrics", + "linkUrl": "d/at-cost-analysis-namespace2/namespace-cost-metrics?&var-namespace=$__cell", + "mappingType": 1, + "pattern": "namespace", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "targets": [ + { + "expr": "sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\"} / 1024 / 1024 / 1024) by (namespace) ", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "", + "refId": "A" + }, + { + "expr": "SUM(container_memory_usage_bytes{image!=\"\",namespace!=\"\"} / 1024 / 1024 / 1024) by (namespace)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "legendFormat": "", + "refId": "C" + } + ], + "title": "Memory requests & utilization by namespace", + "transform": "table", + "type": "table-old" + }, + { + "columns": [ + { + "text": "Avg", + "value": "avg" + } + ], + "datasource": { + "uid": "${datasource}" + }, + "description": "Container RAM usage vs node capacity", + "fontSize": "100%", + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 62 + }, + "hideTimeOverride": true, + "id": 114, + "links": [], + "pageSize": 8, + "repeatDirection": "v", + "scroll": true, + "showHeader": true, + "sort": { + "col": 1, + "desc": true + }, + "styles": [ + { + "alias": "RAM Requests", + "align": "auto", + "colorMode": "value", + "colors": [ + "#ef843c", + "rgba(50, 172, 45, 0.97)", + "rgba(245, 54, 54, 0.9)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #A", + "thresholds": [ + "30", + " 80" + ], + "type": "number", + "unit": "percentunit" + }, + { + "alias": "Node", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "node", + "thresholds": [], + "type": "string", + "unit": "short" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Time", + "thresholds": [], + "type": "hidden", + "unit": "short" + }, + { + "alias": "RAM Usage", + "align": "auto", + "colorMode": "value", + "colors": [ + "#ef843c", + "rgba(50, 172, 45, 0.97)", + "rgba(245, 54, 54, 0.9)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #B", + "thresholds": [ + "25", + " 80" + ], + "type": "number", + "unit": "percent" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "targets": [ + { + "expr": "SUM(label_replace(container_memory_usage_bytes{namespace!=\"\"}, \"node\", \"$1\", \"instance\",\"(.+)\")) by (node) * 100\n/\nSUM(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "B" + }, + { + "expr": "sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\"}) by (node) / SUM(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "A" + } + ], + "title": "Node utilization of allocatable RAM", + "transform": "table", + "type": "table-old" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 72 + }, + "id": 101, + "panels": [], + "title": "Storage Metrics", + "type": "row" + }, + { + "columns": [ + { + "text": "Avg", + "value": "avg" + } + ], + "datasource": { + "uid": "${datasource}" + }, + "fontSize": "100%", + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 73 + }, + "hideTimeOverride": true, + "id": 97, + "links": [], + "pageSize": 8, + "repeatDirection": "v", + "scroll": true, + "showHeader": true, + "sort": { + "col": 4, + "desc": true + }, + "styles": [ + { + "alias": "Node", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "instance", + "thresholds": [], + "type": "string", + "unit": "short" + }, + { + "alias": "PVC Name", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "persistentvolumeclaim", + "thresholds": [], + "type": "number", + "unit": "short" + }, + { + "alias": "Storage Class", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "storageclass", + "thresholds": [], + "type": "number", + "unit": "short" + }, + { + "alias": "Cost", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Time", + "thresholds": [], + "type": "hidden", + "unit": "short" + }, + { + "alias": "Cost", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #A", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "Size (GB)", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #B", + "thresholds": [], + "type": "number", + "unit": "short" + }, + { + "alias": "Usage", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #C", + "thresholds": [], + "type": "number", + "unit": "percentunit" + } + ], + "targets": [ + { + "expr": "SUM(container_fs_limit_bytes{id=\"/\"}) by (instance) / 1024 / 1024 / 1024 * 1.03", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "B" + }, + { + "expr": "SUM(container_fs_limit_bytes{id=\"/\"}) by (instance) / 1024 / 1024 / 1024 * 1.03 * $costStorageStandard\n", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{ persistentvolumeclaim }}", + "refId": "A" + }, + { + "expr": "sum(container_fs_usage_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\"} / container_fs_limit_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\"}) by (instance) \n", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "C" + } + ], + "title": "Local Storage", + "transform": "table", + "type": "table-old" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 73 + }, + "id": 128, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "8.3.2", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "SUM(container_fs_usage_bytes{id=\"/\"}) / SUM(container_fs_limit_bytes{id=\"/\"})", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "reads", + "refId": "D" + } + ], + "thresholds": [], + "title": "Local storage utilization", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percent", + "label": "IOPS", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "columns": [ + { + "text": "Avg", + "value": "avg" + } + ], + "datasource": { + "uid": "${datasource}" + }, + "fontSize": "100%", + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 82 + }, + "hideTimeOverride": true, + "id": 94, + "links": [], + "pageSize": 10, + "repeatDirection": "v", + "scroll": true, + "showHeader": true, + "sort": { + "col": 2, + "desc": true + }, + "styles": [ + { + "alias": "Namespace", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "link": true, + "linkTooltip": "View namespace cost metrics", + "linkUrl": "d/at-cost-analysis-namespace2/namespace-cost-metrics?&var-namespace=$__cell", + "mappingType": 1, + "pattern": "namespace", + "thresholds": [], + "type": "string", + "unit": "short" + }, + { + "alias": "PVC Name", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "persistentvolumeclaim", + "thresholds": [], + "type": "number", + "unit": "short" + }, + { + "alias": "Storage Class", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "storageclass", + "thresholds": [], + "type": "number", + "unit": "short" + }, + { + "alias": "Cost", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Time", + "thresholds": [], + "type": "hidden", + "unit": "short" + }, + { + "alias": "Cost", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #A", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "Usage", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #B", + "thresholds": [], + "type": "number", + "unit": "percentunit" + }, + { + "alias": "Size (GB)", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #C", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "targets": [ + { + "expr": "sum (\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n * on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace)\n) by (namespace,persistentvolumeclaim,storageclass) / 1024 / 1024 /1024\n\nor\n\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n * on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace)\n) by (namespace,persistentvolumeclaim,storageclass) / 1024 / 1024 /1024\n\n\n", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "C" + }, + { + "expr": "sum (\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n * on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace)\n) by (namespace,persistentvolumeclaim,storageclass) / 1024 / 1024 /1024 * $costStorageSSD\n\nor\n\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n * on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace)\n) by (namespace,persistentvolumeclaim,storageclass) / 1024 / 1024 /1024 * $costStorageStandard\n", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{ persistentvolumeclaim }}", + "refId": "A" + }, + { + "expr": "sum(kubelet_volume_stats_used_bytes) by (persistentvolumeclaim, namespace) \n/\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n * on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace)\n) by (namespace,persistentvolumeclaim)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "B" + } + ], + "title": "Persistent Volume Claims", + "transform": "table", + "type": "table-old" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 82 + }, + "id": 132, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "8.3.2", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "SUM(rate(node_disk_reads_completed_total[10m])) or SUM(rate(node_disk_reads_completed[10m]))\n", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "reads", + "refId": "D" + }, + { + "expr": "SUM(rate(node_disk_writes_completed_total[10m])) or SUM(rate(node_disk_writes_completed[10m]))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "writes", + "refId": "A" + } + ], + "thresholds": [], + "title": "Disk IOPS", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": "IOPS", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 92 + }, + "id": 122, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "8.3.2", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "SUM( kubelet_volume_stats_inodes_used / kubelet_volume_stats_inodes) by (persistentvolumeclaim) * 100", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "refId": "D" + } + ], + "thresholds": [], + "title": "Inode usage", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percent", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 101 + }, + "id": 127, + "panels": [], + "title": "Network", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 102 + }, + "id": 123, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "8.3.2", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum (rate (node_network_transmit_bytes_total{}[60m]))\n", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "node_out", + "refId": "B" + }, + { + "expr": "SUM ( rate(node_network_transmit_bytes_total{device=\"eth0\"}[60m]))", + "format": "time_series", + "instant": false, + "intervalFactor": 1, + "legendFormat": "eth0 out", + "refId": "C" + } + ], + "thresholds": [], + "title": "Node network transmit", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decbytes", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + ], + "refresh": "15m", + "schemaVersion": 33, + "style": "dark", + "tags": [ + "cost", + "utilization", + "metrics" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": "23.076", + "value": "23.076" + }, + "hide": 0, + "label": "CPU", + "name": "costcpu", + "options": [ + { + "selected": true, + "text": "23.076", + "value": "23.076" + } + ], + "query": "23.076", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": true, + "text": "5.10", + "value": "5.10" + }, + "hide": 0, + "label": "PE CPU", + "name": "costpcpu", + "options": [ + { + "selected": true, + "text": "5.10", + "value": "5.10" + } + ], + "query": "5.10", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": true, + "text": "3.25", + "value": "3.25" + }, + "hide": 0, + "label": "RAM", + "name": "costram", + "options": [ + { + "selected": true, + "text": "3.25", + "value": "3.25" + } + ], + "query": "3.25", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": true, + "text": "0.6862", + "value": "0.6862" + }, + "hide": 0, + "label": "PE RAM", + "name": "costpram", + "options": [ + { + "selected": true, + "text": "0.6862", + "value": "0.6862" + } + ], + "query": "0.6862", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": true, + "text": "0.040", + "value": "0.040" + }, + "hide": 0, + "label": "Storage", + "name": "costStorageStandard", + "options": [ + { + "selected": true, + "text": "0.040", + "value": "0.040" + } + ], + "query": "0.040", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": true, + "text": ".17", + "value": ".17" + }, + "hide": 0, + "label": "SSD", + "name": "costStorageSSD", + "options": [ + { + "selected": true, + "text": ".17", + "value": ".17" + } + ], + "query": ".17", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": true, + "text": ".12", + "value": ".12" + }, + "hide": 0, + "label": "Egress", + "name": "costEgress", + "options": [ + { + "selected": true, + "text": ".12", + "value": ".12" + } + ], + "query": ".12", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": true, + "text": "30", + "value": "30" + }, + "hide": 0, + "label": "Discount", + "name": "costDiscount", + "options": [ + { + "selected": true, + "text": "30", + "value": "30" + } + ], + "query": "30", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": false, + "text": "default-kubecost", + "value": "default-kubecost" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time": { + "from": "now-24h", + "to": "now" + }, + "timepicker": { + "hidden": false, + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "browser", + "title": "Deprecated - Cluster cost & utilization metrics", + "uid": "cluster-costs", + "version": 1, + "weekStart": "" + } \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/deployment-utilization.json b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/deployment-utilization.json new file mode 100644 index 000000000..1fd2b1d9e --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/deployment-utilization.json @@ -0,0 +1,1386 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Monitors Kubernetes deployments in cluster using Prometheus and kube-state-metrics. Shows resource utilization of deployments, daemonsets, and statefulsets.", + "editable": true, + "gnetId": 8588, + "graphTooltip": 0, + "id": 2, + "iteration": 1586200623748, + "links": [], + "panels": [ + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(50, 172, 45, 0.97)", + "rgba(237, 129, 40, 0.89)", + "rgba(245, 54, 54, 0.9)" + ], + "datasource": "${datasource}", + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 0, + "y": 0 + }, + "height": "180px", + "id": 1, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum (container_memory_working_set_bytes{container!=\"\",pod_name=~\"^$Deployment$Statefulset$Daemonset.*$\", kubernetes_io_hostname=~\"^$Node$\", pod_name!=\"\"}) / sum (kube_node_status_allocatable{resource=\"memory\", unit=\"byte\", node=~\"^$Node.*$\"}) * 100", + "format": "time_series", + "interval": "10s", + "intervalFactor": 1, + "refId": "A", + "step": 900 + } + ], + "thresholds": "65, 90", + "title": "Deployment memory usage", + "transparent": false, + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(50, 172, 45, 0.97)", + "rgba(237, 129, 40, 0.89)", + "rgba(245, 54, 54, 0.9)" + ], + "datasource": "${datasource}", + "decimals": 2, + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 8, + "y": 0 + }, + "height": "180px", + "id": 2, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum (rate (container_cpu_usage_seconds_total{pod_name=~\"^$Deployment$Statefulset$Daemonset.*$\", kubernetes_io_hostname=~\"^$Node$\"}[2m])) / sum (machine_cpu_cores{kubernetes_io_hostname=~\"^$Node$\"}) * 100", + "format": "time_series", + "interval": "10s", + "intervalFactor": 1, + "refId": "A", + "step": 900 + } + ], + "thresholds": "65, 90", + "title": "Deployment CPU usage", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(50, 172, 45, 0.97)", + "rgba(237, 129, 40, 0.89)", + "rgba(245, 54, 54, 0.9)" + ], + "datasource": "${datasource}", + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 16, + "y": 0 + }, + "height": "180px", + "id": 3, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "(((sum(kube_deployment_status_replicas{deployment=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_statefulset_replicas{statefulset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_daemonset_status_desired_number_scheduled{daemonset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0))) - ((sum(kube_deployment_status_replicas_available{deployment=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_statefulset_status_replicas{statefulset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_daemonset_status_number_ready{daemonset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)))) / ((sum(kube_deployment_status_replicas{deployment=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_statefulset_replicas{statefulset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_daemonset_status_desired_number_scheduled{daemonset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0))) * 100", + "format": "time_series", + "intervalFactor": 2, + "refId": "A", + "step": 1800 + } + ], + "thresholds": "1,30", + "title": "Unavailable Replicas", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "${datasource}", + "editable": true, + "error": false, + "format": "bytes", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 0, + "y": 5 + }, + "height": "100px", + "id": 4, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(container_memory_working_set_bytes{container!=\"\",pod_name=~\"^$Deployment$Statefulset$Daemonset.*$\", kubernetes_io_hostname=~\"^$Node$\", pod_name!=\"\"})", + "format": "time_series", + "intervalFactor": 2, + "refId": "A", + "step": 1800 + } + ], + "thresholds": "", + "title": "Used", + "type": "singlestat", + "valueFontSize": "50%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "${datasource}", + "editable": true, + "error": false, + "format": "bytes", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 4, + "y": 5 + }, + "height": "100px", + "id": 5, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum (kube_node_status_allocatable{resource=\"memory\", unit=\"byte\", node=~\"^$Node.*$\"})", + "format": "time_series", + "intervalFactor": 2, + "refId": "A", + "step": 1800 + } + ], + "thresholds": "", + "title": "Total", + "type": "singlestat", + "valueFontSize": "50%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "${datasource}", + "editable": true, + "error": false, + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 8, + "y": 5 + }, + "height": "100px", + "id": 6, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": " cores", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum (rate (container_cpu_usage_seconds_total{pod_name=~\"^$Deployment$Statefulset$Daemonset.*$\", kubernetes_io_hostname=~\"^$Node$\"}[5m]))", + "format": "time_series", + "intervalFactor": 2, + "refId": "A", + "step": 1800 + } + ], + "thresholds": "", + "title": "Used", + "type": "singlestat", + "valueFontSize": "50%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "${datasource}", + "editable": true, + "error": false, + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 12, + "y": 5 + }, + "height": "100px", + "id": 7, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": " cores", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum (machine_cpu_cores{kubernetes_io_hostname=~\"^$Node$\"})", + "intervalFactor": 2, + "refId": "A", + "step": 1800 + } + ], + "thresholds": "", + "title": "Total", + "type": "singlestat", + "valueFontSize": "50%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "${datasource}", + "editable": true, + "error": false, + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 16, + "y": 5 + }, + "height": "100px", + "id": 8, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "(sum(kube_deployment_status_replicas_available{deployment=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_statefulset_status_replicas{statefulset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_daemonset_status_number_ready{daemonset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0))", + "format": "time_series", + "intervalFactor": 2, + "refId": "A", + "step": 1800 + } + ], + "thresholds": "", + "title": "Available (cluster)", + "type": "singlestat", + "valueFontSize": "50%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "${datasource}", + "editable": true, + "error": false, + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 20, + "y": 5 + }, + "height": "100px", + "id": 9, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "(sum(kube_deployment_status_replicas{deployment=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_statefulset_replicas{statefulset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_daemonset_status_desired_number_scheduled{daemonset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0))", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{ $Daemonset }}", + "refId": "A", + "step": 1800 + } + ], + "thresholds": "", + "title": "Total (cluster)", + "type": "singlestat", + "valueFontSize": "50%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "decimals": 3, + "editable": true, + "error": false, + "fill": 0, + "grid": {}, + "gridPos": { + "h": 11, + "w": 24, + "x": 0, + "y": 8 + }, + "height": "", + "id": 10, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sideWidth": null, + "sort": "current", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "/avlbl.*/", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum (irate (container_cpu_usage_seconds_total{container!=\"\",image!=\"\",name=~\"^k8s_.*\",io_kubernetes_container_name!=\"POD\",pod_name=~\"^$Deployment$Statefulset$Daemonset.*$\",kubernetes_io_hostname=~\"^$Node$\"}[5m])) by (pod_name,kubernetes_io_hostname)", + "format": "time_series", + "hide": false, + "interval": "10s", + "intervalFactor": 1, + "legendFormat": "usage: {{ kubernetes_io_hostname }} | {{ pod_name }} ", + "metric": "container_cpu", + "refId": "A", + "step": 60 + }, + { + "expr": "sum (kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", pod=~\"^$Deployment$Statefulset$Daemonset.*$\",node=~\"^$Node$\"}) by (pod,node)", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "rqst: {{ node }} | {{ pod }}", + "refId": "B", + "step": 120 + }, + { + "expr": "sum ((kube_node_status_allocatable{resource=\"cpu\", unit=\"core\", node=~\"^$Node$\"})) by (node)", + "format": "time_series", + "hide": true, + "intervalFactor": 2, + "legendFormat": "avlbl: {{ node }}", + "refId": "C", + "step": 30 + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "CPU usage & requests", + "tooltip": { + "msResolution": true, + "shared": true, + "sort": 2, + "value_type": "cumulative" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": "cores", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "decimals": 2, + "editable": true, + "error": false, + "fill": 0, + "grid": {}, + "gridPos": { + "h": 13, + "w": 24, + "x": 0, + "y": 19 + }, + "id": 11, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sideWidth": null, + "sort": "current", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "/^avlbl.*$/", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "max (container_memory_working_set_bytes{container!=\"POD\",container!=\"\",id!=\"/\",pod_name=~\"^$Deployment$Statefulset$Daemonset.*$\",kubernetes_io_hostname=~\"^$Node$\"}) by (pod_name,kubernetes_io_hostname)", + "format": "time_series", + "hide": false, + "interval": "10s", + "intervalFactor": 1, + "legendFormat": "usage: {{kubernetes_io_hostname }} | {{ pod_name }}", + "metric": "container_memory_usage:sort_desc", + "refId": "A", + "step": 60 + }, + { + "expr": "sum ((kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", pod=~\"^$Deployment$Statefulset$Daemonset.*$\",node=~\"^$Node$\"})) by (pod,node)", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "rqst: {{ node }} | {{ pod }}", + "refId": "B", + "step": 120 + }, + { + "expr": "sum ((kube_node_status_allocatable{resource=\"memory\", unit=\"byte\", node=~\"^$Node$\"})) by (node)", + "format": "time_series", + "hide": true, + "intervalFactor": 2, + "legendFormat": "avlbl: {{ node }}", + "refId": "C", + "step": 30 + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Memory usage & requests", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 2, + "value_type": "cumulative" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fill": 1, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 32 + }, + "id": 12, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "sort": "current", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "100 * (kubelet_volume_stats_used_bytes{kubernetes_io_hostname=~\"^$Node$\", persistentvolumeclaim=~\".*$Deployment$Statefulset$Daemonset.*$\"} / kubelet_volume_stats_capacity_bytes{kubernetes_io_hostname=~\"^$Node$\", persistentvolumeclaim=~\".*$Deployment$Statefulset$Daemonset.*$\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{ persistentvolumeclaim }} | {{ kubernetes_io_hostname }}", + "refId": "A", + "step": 120 + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Disk Usage", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percent", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "decimals": 2, + "editable": true, + "error": false, + "fill": 1, + "grid": {}, + "gridPos": { + "h": 13, + "w": 24, + "x": 0, + "y": 41 + }, + "id": 13, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sideWidth": null, + "sort": "current", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum (rate (container_network_receive_bytes_total{id!=\"/\",pod_name=~\"^$Deployment$Statefulset$Daemonset.*$\",kubernetes_io_hostname=~\"^$Node$\"}[2m])) by (pod_name, kubernetes_io_hostname)", + "format": "time_series", + "interval": "10s", + "intervalFactor": 1, + "legendFormat": "-> {{ kubernetes_io_hostname }} | {{ pod_name }}", + "metric": "network", + "refId": "A", + "step": 60 + }, + { + "expr": "- sum( rate (container_network_transmit_bytes_total{id!=\"/\",pod_name=~\"^$Deployment$Statefulset$Daemonset.*$\",kubernetes_io_hostname=~\"^$Node$\"}[2m])) by (pod_name, kubernetes_io_hostname)", + "format": "time_series", + "interval": "10s", + "intervalFactor": 1, + "legendFormat": "<- {{ kubernetes_io_hostname }} | {{ pod_name }}", + "metric": "network", + "refId": "B", + "step": 60 + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "All processes network I/O", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 2, + "value_type": "cumulative" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "Bps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": false, + "schemaVersion": 16, + "style": "dark", + "tags": [ + "kubecost", + "utilization", + "metrics" + ], + "templating": { + "list": [ + { + "allValue": "()", + "current": { + "selected": false, + "tags": [], + "text": "All", + "value": "$__all" + }, + "datasource": "${datasource}", + "hide": 0, + "includeAll": true, + "label": null, + "multi": false, + "name": "Deployment", + "options": [], + "query": "label_values(deployment)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": "()", + "current": { + "text": "All", + "value": "$__all" + }, + "datasource": "${datasource}", + "hide": 0, + "includeAll": true, + "label": null, + "multi": false, + "name": "Statefulset", + "options": [], + "query": "label_values(statefulset)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": "()", + "current": { + "text": "All", + "value": "$__all" + }, + "datasource": "${datasource}", + "hide": 0, + "includeAll": true, + "label": null, + "multi": false, + "name": "Daemonset", + "options": [], + "query": "label_values(daemonset)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".*", + "current": { + "text": "All", + "value": "$__all" + }, + "datasource": "${datasource}", + "hide": 0, + "includeAll": true, + "label": null, + "multi": false, + "name": "Node", + "options": [], + "query": "label_values(kubernetes_io_hostname)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": true, + "text": "default-kubecost", + "value": "default-kubecost" + }, + "error": null, + "hide": 0, + "includeAll": false, + "label": null, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time": { + "from": "now-24h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "browser", + "title": "Deprecated - Deployment/Statefulset/Daemonset utilization metrics", + "uid": "deployment-metrics", + "version": 2 +} diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/grafana-templates/aggregator-dashboard.json b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/grafana-templates/aggregator-dashboard.json new file mode 100644 index 000000000..e7c5b3691 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/grafana-templates/aggregator-dashboard.json @@ -0,0 +1,668 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 7, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(rate(container_fs_writes_bytes_total{pod=~\".+-aggregator-0\",namespace=~\"$namespace\"}[2m])) by (namespace)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Storage Write", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(rate(container_fs_reads_bytes_total{pod=~\".+-aggregator-0\",namespace=~\"$namespace\"}[2m])) by (namespace)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Storage Read", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(container_memory_working_set_bytes{container=\"aggregator\",pod!=\"\",namespace=~\"$namespace\"} ) by (namespace)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Memory Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(rate(node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate\r\n{container=\"aggregator\",pod!=\"\",namespace=~\"$namespace\"}[2m])) by (namespace)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "CPU Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(kubelet_volume_stats_available_bytes{persistentvolumeclaim=~\"aggregator.+\",namespace=~\"$namespace\"}) by (namespace)", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "C" + } + ], + "title": "Storage Available", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(rate(container_network_receive_bytes_total{pod=~\".+aggregator-0\",namespace=~\"$namespace\"}[2m])) by (namespace)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Network Receive Bytes", + "type": "timeseries" + } + ], + "refresh": "30s", + "schemaVersion": 39, + "tags": [ + "utilization", + "metrics", + "kubecost" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": "Prometheus", + "value": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(container_memory_working_set_bytes{container=\"aggregator\"},namespace)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "namespace", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(container_memory_working_set_bytes{container=\"aggregator\"},namespace)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-1d", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Kubecost Aggregator Metrics", + "uid": "kubecost_aggregator_metrics", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/grafana-templates/multi-cluster-container-stats.json b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/grafana-templates/multi-cluster-container-stats.json new file mode 100644 index 000000000..5c592b339 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/grafana-templates/multi-cluster-container-stats.json @@ -0,0 +1,787 @@ +{ + "__inputs": [ + { + "name": "DS_THANOS", + "label": "Thanos", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "10.3.1" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": 9063, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "description": "Maximum CPU Core Usage vs avg Requested", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 94, + "links": [], + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "editorMode": "code", + "expr": "max(irate(container_cpu_usage_seconds_total\r\n {cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\", container=~\"$container\", container!=\"POD\",container!=\"\"}\r\n [$__rate_interval])) \r\n by (cluster_id, namespace, pod, container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}/{{container}} (usage max)", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "editorMode": "code", + "exemplar": true, + "expr": "avg(kube_pod_container_resource_requests\r\n {cluster_id=\"$cluster\",resource=\"cpu\",unit=\"core\",namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$container\",container!=\"POD\"}\r\n ) \r\nby (cluster_id,namespace,pod,container)", + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}/{{container}} (requested)", + "range": true, + "refId": "B" + } + ], + "timeFrom": "", + "title": "CPU Core Usage vs Requested", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "description": "Max memory used vs avg requested", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 96, + "links": [], + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "asc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "editorMode": "code", + "expr": "max(max_over_time(container_memory_working_set_bytes\r\n {namespace=~\"$namespace\",pod=~\"$pod\",cluster_id=\"$cluster\",container=~\"$container\",container!=\"POD\",container!=\"\"}\r\n [$__rate_interval])) \r\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}/{{container}} (usage max)", + "metric": "container_cpu", + "refId": "MEMORY_USAGE", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "editorMode": "code", + "expr": "avg(kube_pod_container_resource_requests\n {resource=\"memory\",unit=\"byte\",cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\", container=~\"$container\",container!=\"POD\"}\n )\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}/{{container}} (requested)", + "refId": "MEMORY_REQUESTED" + } + ], + "timeFrom": "", + "title": "Memory Usage vs Requested", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "description": "Network traffic by pod", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 7 + }, + "id": 95, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "editorMode": "code", + "expr": "sum(irate(container_network_receive_bytes_total\n {cluster_id=~\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\"}\n [$__rate_interval])) \nby (cluster_id, namespace, pod)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}<- in", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "editorMode": "code", + "expr": "- sum(irate(container_network_transmit_bytes_total\n {cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\"}\n [$__rate_interval])) \nby (cluster_id, namespace, pod)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}-> out", + "refId": "B" + } + ], + "timeFrom": "", + "title": "Network IO", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "description": "Disk read writes", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 7 + }, + "id": 97, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "editorMode": "code", + "expr": "sum(irate(container_fs_writes_bytes_total\r\n {cluster_id=\"$cluster\",namespace=~\"$namespace\",container!=\"POD\",pod!=\"\",pod=~\"$pod\",container=~\"$container\"}\r\n [$__rate_interval])) \r\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{pod}}/{{container}}<- write", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "editorMode": "code", + "expr": "- sum(irate(container_fs_reads_bytes_total\r\n {cluster_id=\"$cluster\",namespace=~\"$namespace\",container!=\"POD\",pod!=\"\",pod=~\"$pod\",container=~\"$container\"}\r\n [$__rate_interval])) \r\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{pod}}/{{container}}-> read", + "refId": "B" + } + ], + "timeFrom": "", + "title": "Disk IO", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "description": "This graph shows the % of periods where a pod is being throttled. Values range from 0-100", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 14 + }, + "id": 99, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "editorMode": "code", + "expr": "100\n * sum by(cluster_id, namespace, pod, container) (increase(container_cpu_cfs_throttled_periods_total{container!=\"\",cluster_id=\"$cluster\", namespace=~\"$namespace\", pod=~\"$pod\", container=~\"$container\", container!=\"POD\"}[$__rate_interval]))\n / sum by(cluster_id,namespace,pod,container) (increase(container_cpu_cfs_periods_total{container!=\"\",cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$container\",container!=\"POD\"}[$__rate_interval]))", + "format": "time_series", + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "", + "refId": "B" + } + ], + "timeFrom": "", + "title": "CPU throttle percent", + "type": "timeseries" + } + ], + "refresh": "", + "revision": 1, + "schemaVersion": 39, + "tags": [ + "utilization", + "metrics", + "kubecost" +], + "templating": { + "list": [ + { + "current": {}, + "definition": "label_values(cluster_id)", + "hide": 0, + "includeAll": false, + "multi": false, + "name": "cluster", + "options": [], + "query": { + "query": "label_values(cluster_id)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": {}, + "definition": "label_values(kube_namespace_labels{cluster_id=\"$cluster\"}, namespace) ", + "hide": 0, + "includeAll": true, + "label": "", + "multi": false, + "name": "namespace", + "options": [], + "query": { + "query": "label_values(kube_namespace_labels{cluster_id=\"$cluster\"}, namespace) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": {}, + "definition": "label_values(kube_pod_labels{cluster_id=\"$cluster\",namespace=~\"$namespace\"}, pod) ", + "hide": 0, + "includeAll": true, + "label": "pod", + "multi": false, + "name": "pod", + "options": [], + "query": { + "query": "label_values(kube_pod_labels{cluster_id=\"$cluster\",namespace=~\"$namespace\"}, pod) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": {}, + "definition": "label_values(container_memory_working_set_bytes{cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\", container!=\"POD\"}, container) ", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "container", + "options": [], + "query": { + "query": "label_values(container_memory_working_set_bytes{cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\", container!=\"POD\"}, container) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-2d", + "to": "now" + }, + "timepicker": { + "hidden": false, + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "browser", + "title": "Pod utilization metrics (multi-cluster)", + "uid": "at-cost-analysis-pod2", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/grafana-templates/multi-cluster-disk-usage.json b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/grafana-templates/multi-cluster-disk-usage.json new file mode 100644 index 000000000..6dc0b153c --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/grafana-templates/multi-cluster-disk-usage.json @@ -0,0 +1,571 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "Prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "10.4.2" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.0.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(container_fs_limit_bytes{instance=~'$disk', device!=\"tmpfs\", id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance)", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}}/{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Disk Size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.0.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(container_fs_usage_bytes{instance=~'$disk',id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance) / sum(container_fs_limit_bytes{instance=~'$disk',device!=\"tmpfs\", id=\"/\", cluster_id=~'$cluster'}) by (cluster_id,instance)", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}}-{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Disk Utilization", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 9 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.0.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "1 - sum(container_fs_inodes_free{instance=~'$disk',id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance) / sum(container_fs_inodes_total{instance=~'$disk',id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}}/{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "iNode Utilization", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 9 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.0.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(container_fs_usage_bytes{instance=~'$disk',id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance)", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}}/{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Disk Usage", + "type": "timeseries" + } + ], + "schemaVersion": 39, + "tags": [ + "kubecost", + "utilization", + "metrics" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "Prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(cluster_id)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "cluster", + "options": [], + "query": { + "query": "label_values(cluster_id)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(container_fs_limit_bytes{cluster_id=~\"$cluster\"}, instance)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "disk", + "options": [], + "query": { + "query": "label_values(container_fs_limit_bytes{cluster_id=~\"$cluster\"}, instance)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-7d", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Attached disk metrics (multi-cluster)", + "uid": "nBH7qBgMk", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/grafana-templates/multi-cluster-network-transfer-data.json b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/grafana-templates/multi-cluster-network-transfer-data.json new file mode 100644 index 000000000..40bf4e787 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/grafana-templates/multi-cluster-network-transfer-data.json @@ -0,0 +1,685 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "Prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "10.4.2" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "https://docs.kubecost.com/install-and-configure/advanced-configuration/network-costs-configuration", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 12, + "panels": [], + "title": "Network Data Transfers (negative is egress data)", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 11, + "x": 0, + "y": 1 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(kubecost_pod_network_ingress_bytes_total\n {namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", service=~\"$service\"}\n [1h]\n ))\nby($aggregation) ", + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "-sum(increase(kubecost_pod_network_egress_bytes_total\n {namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", service=~\"$service\"}\n [1h]\n ))\nby($aggregation) ", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "All Data", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 13, + "x": 11, + "y": 1 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(kubecost_pod_network_ingress_bytes_total\n {internet=\"true\", namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", service=~\"$service\"}\n [1h]\n))\nby($aggregation) ", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "- sum(increase(kubecost_pod_network_egress_bytes_total\n {internet=\"true\", namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", service=~\"$service\"}\n [1h]))\nby($aggregation) ", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "Internet Data", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Cross region and cross zone subnets must be defined via the configMap. \nSee: \n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 11, + "x": 0, + "y": 15 + }, + "id": 9, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(kubecost_pod_network_ingress_bytes_total\n {internet=\"false\",namespace=~\"$namespace\",cluster_id=~\"$cluster\",pod_name=~\"$pod_name\", sameRegion=\"false\",sameZone=\"false\", service=~\"$service\"}\n [1h]))\nby($aggregation)", + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "- sum(increase(kubecost_pod_network_egress_bytes_total\n {internet=\"false\", namespace=~\"$namespace\",cluster_id=~\"$cluster\",pod_name=~\"$pod_name\",sameRegion=\"false\", sameZone=\"false\", service=~\"$service\"}\n [1h]))\nby($aggregation) ", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "Cross Region Data", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Cross region and cross zone subnets must be defined via the configMap. \nSee: \n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 13, + "x": 11, + "y": 15 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(kubecost_pod_network_ingress_bytes_total\n {internet=\"false\", namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", sameRegion=\"true\", sameZone=\"false\", service=~\"$service\"}\n [1h]))\nby($aggregation)", + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "- sum(increase(kubecost_pod_network_egress_bytes_total\n {internet=\"false\", namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", sameRegion=\"true\", sameZone=\"false\", service=~\"$service\"}\n [1h]))\nby($aggregation)", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "Cross Zone Data", + "type": "timeseries" + } + ], + "refresh": "", + "revision": 1, + "schemaVersion": 39, + "tags": [ + "kubecost" + ], + "templating": { + "list": [ + { + "current": {}, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": false, + "text": "namespace", + "value": "namespace" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "aggregation", + "options": [ + { + "selected": false, + "text": "cluster_id", + "value": "cluster_id" + }, + { + "selected": true, + "text": "namespace", + "value": "namespace" + }, + { + "selected": false, + "text": "pod_name", + "value": "pod_name" + } + ], + "query": "cluster_id, namespace, pod_name", + "queryValue": "", + "skipUrlSync": false, + "type": "custom" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(cluster_id)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "cluster", + "options": [], + "query": { + "query": "label_values(cluster_id)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": true, + "text": "kubecost", + "value": "kubecost" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_namespace_labels{cluster_id=~\"$cluster\"}, namespace) ", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "namespace", + "options": [], + "query": { + "query": "label_values(kube_namespace_labels{cluster_id=~\"$cluster\"}, namespace) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kubecost_pod_network_egress_bytes_total{cluster_id=~\"$cluster\", namespace=~\"$namespace\"},pod_name)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "pod_name", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kubecost_pod_network_egress_bytes_total{cluster_id=~\"$cluster\", namespace=~\"$namespace\"},pod_name)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "filters": [], + "hide": 0, + "name": "filter", + "skipUrlSync": false, + "type": "adhoc" + }, + { + "current": {}, + "definition": "label_values(kubecost_pod_network_egress_bytes_total{namespace=~\"$namespace\"},service)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "service", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kubecost_pod_network_egress_bytes_total{namespace=~\"$namespace\"},service)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-7d", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Kubecost Network Costs Metrics", + "uid": "kubecost-networkCosts-metrics", + "version": 8, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/kubernetes-resource-efficiency.json b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/kubernetes-resource-efficiency.json new file mode 100644 index 000000000..156b3c292 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/kubernetes-resource-efficiency.json @@ -0,0 +1,408 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 29, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 2, + "panels": [], + "title": "Requests - Usage (negative values are unused reservations)", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 16, + "w": 24, + "x": 0, + "y": 1 + }, + "id": 4, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum by ($aggregation) (\n (sum by (cluster_id,namespace,pod,container) (container_memory_usage_bytes{cluster_id=~\"$cluster\",namespace=~\"$namespace\",container=~\"$container\",container!=\"POD\",container!=\"\"}))\n -(sum by (cluster_id,namespace,pod,container) (kube_pod_container_resource_requests{resource=\"memory\",unit=\"byte\",cluster_id=~\"$cluster\",namespace=~\"$namespace\",container=~\"$container\",container!=\"POD\",container!=\"\"}))\n)", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum by ($aggregation) (\n -(sum by (cluster_id,namespace,pod,container) (kube_pod_container_resource_requests{resource=\"memory\",unit=\"byte\",cluster_id=~\"$cluster\",namespace=~\"$namespace\",container=~\"$container\",container!=\"POD\",container!=\"\"}))\n)", + "hide": true, + "legendFormat": "{{$aggregation}} Request", + "range": true, + "refId": "B" + } + ], + "title": "Memory Request-Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 24, + "x": 0, + "y": 17 + }, + "id": 6, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum by ($aggregation)(\n (sum by (cluster_id,namespace,pod,container) (rate(container_cpu_usage_seconds_total{cluster_id=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\", container!=\"POD\",container!=\"\"}[1h])))\n - \n (sum by (cluster_id,namespace,pod,container) (kube_pod_container_resource_requests{resource=\"cpu\",cluster_id=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\", container!=\"POD\",container!=\"\"}))\n)\n \n", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "-sum by ($aggregation)(\n (sum by (cluster_id,namespace,pod,container) (kube_pod_container_resource_requests{resource=\"cpu\",cluster_id=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\", container!=\"POD\",container!=\"\"}))\n)", + "hide": true, + "legendFormat": "{{$aggregation}} Request", + "range": true, + "refId": "B" + } + ], + "title": "CPU Request-Usage", + "type": "timeseries" + } + ], + "schemaVersion": 37, + "style": "dark", + "tags": [ + "utilization", + "metrics", + "kubecost" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "default", + "value": "default" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": true, + "text": "namespace", + "value": "namespace" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "aggregation", + "options": [ + { + "selected": false, + "text": "cluster_id", + "value": "cluster_id" + }, + { + "selected": true, + "text": "namespace", + "value": "namespace" + }, + { + "selected": false, + "text": "container", + "value": "container" + } + ], + "query": "cluster_id,namespace,container", + "queryValue": "", + "skipUrlSync": false, + "type": "custom" + }, + { + "current": { + "selected": true, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_namespace_labels, cluster_id)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "cluster", + "options": [], + "query": { + "query": "label_values(kube_namespace_labels, cluster_id)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": true, + "text": "kubecost", + "value": "kubecost" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_namespace_labels{cluster_id=~\"$cluster\"}, namespace) ", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "namespace", + "options": [], + "query": { + "query": "label_values(kube_namespace_labels{cluster_id=~\"$cluster\"}, namespace) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(container_memory_working_set_bytes{cluster_id=~\"$cluster\",namespace=~\"$namespace\", container!=\"POD\"}, container) ", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "container", + "options": [], + "query": { + "query": "label_values(container_memory_working_set_bytes{cluster_id=~\"$cluster\",namespace=~\"$namespace\", container!=\"POD\"}, container) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Kubernetes Resource Efficiency", + "uid": "kubernetes-resource-efficiency", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/label-cost-utilization.json b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/label-cost-utilization.json new file mode 100644 index 000000000..dc1963edb --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/label-cost-utilization.json @@ -0,0 +1,1146 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "iteration": 1645115160709, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Monthly projected CPU cost given last 10m", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 0 + }, + "hideTimeOverride": true, + "id": 15, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P0C970EB638C812D0" + }, + "exemplar": false, + "expr": "sum(\n avg(container_cpu_allocation) by (pod,node)\n\n * on (node) group_left()\n avg(avg_over_time(node_cpu_hourly_cost[10m])) by (node)\n\n * on (pod) group_left()\n label_replace(\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod),\n \"pod_name\",\n \"$1\", \n \"pod\", \n \"(.+)\"\n )\n) * 730", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "CPU Cost", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Based on CPU usage over last 24 hours", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 0 + }, + "hideTimeOverride": true, + "id": 16, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P0C970EB638C812D0" + }, + "exemplar": false, + "expr": "sum(\n avg(container_memory_allocation_bytes) by (pod,node) / 1024 / 1024 / 1024\n\n * on (node) group_left()\n avg(avg_over_time(node_ram_hourly_cost[10m])) by (node)\n\n * on (pod) group_left()\n label_replace(\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod),\n \"pod_name\",\n \"$1\", \n \"pod\", \n \"(.+)\"\n )\n) * 730", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "Memory Cost", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 0 + }, + "hideTimeOverride": true, + "id": 21, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P0C970EB638C812D0" + }, + "exemplar": false, + "expr": "sum(\n sum(kube_persistentvolumeclaim_info{storageclass!=\".*ssd.*\"}) by (persistentvolumeclaim, storageclass)\n * on (persistentvolumeclaim) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim)\n * on (persistentvolumeclaim) group_left(label_app)\n max(kube_persistentvolumeclaim_labels{label_$label=~\"$label_value\"}) by (persistentvolumeclaim) or up * 0\n) / 1024 / 1024 /1024 * .04 \n\n+\n\nsum(\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, storageclass)\n * on (persistentvolumeclaim) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim)\n * on (persistentvolumeclaim) group_left(label_app)\n max(kube_persistentvolumeclaim_labels{label_$label=~\"$label_value\"}) by (persistentvolumeclaim) or up * 0\n) / 1024 / 1024 /1024 * .17 \n", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "Storage Cost", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Cost of memory + CPU usage", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 18, + "y": 0 + }, + "hideTimeOverride": true, + "id": 20, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P0C970EB638C812D0" + }, + "exemplar": false, + "expr": "# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CPU ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nsum(\n avg(container_cpu_allocation) by (pod,node)\n\n * on (node) group_left()\n avg(avg_over_time(node_cpu_hourly_cost[10m])) by (node)\n\n * on (pod) group_left()\n label_replace(\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod),\n \"pod_name\",\n \"$1\", \n \"pod\", \n \"(.+)\"\n )\n) * 730\n\n#END CPU\n+\n\n# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Memory ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nsum(\n avg(container_memory_allocation_bytes) by (pod,node) / 1024 / 1024 / 1024\n\n * on (node) group_left()\n avg(avg_over_time(node_ram_hourly_cost[10m])) by (node)\n\n * on (pod) group_left()\n label_replace(\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod),\n \"pod_name\",\n \"$1\", \n \"pod\", \n \"(.+)\"\n )\n) * 730\n\n# END MEMORY\n\n+\n\n# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ STORAGE ~~~~~~~~~~~~~~~~~~~~~~~~~\n\nsum(\n sum(kube_persistentvolumeclaim_info{storageclass!=\".*ssd.*\"}) by (persistentvolumeclaim, storageclass)\n * on (persistentvolumeclaim) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim)\n * on (persistentvolumeclaim) group_left(label_app)\n max(kube_persistentvolumeclaim_labels{label_$label=~\"$label_value\"}) by (persistentvolumeclaim) or up * 0\n) / 1024 / 1024 /1024 * .04 \n\n+\n\nsum(\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, storageclass)\n * on (persistentvolumeclaim) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim)\n * on (persistentvolumeclaim) group_left(label_app)\n max(kube_persistentvolumeclaim_labels{label_$label=~\"$label_value\"}) by (persistentvolumeclaim) or up * 0\n) / 1024 / 1024 /1024 * .17 \n\n\n# END STORAGE\n", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "Total Cost", + "type": "stat" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 0, + "y": 5 + }, + "id": 10, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "expr": "sum(\n sum (kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\"}) by (pod)\n * on (pod) group_left()\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod)\n or up * 0\n) ", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + } + ], + "title": "CPU Request", + "type": "stat" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 3, + "y": 5 + }, + "id": 17, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "expr": "sum(\n label_replace(\n sum(rate(container_cpu_usage_seconds_total{image!=\"\",container_name!=\"POD\"}[1h])) by (kubernetes_io_hostname,pod_name),\n \"node\",\n \"$1\", \n \"kubernetes_io_hostname\", \n \"(.+)\"\n ) \n * on (pod_name) group_left()\n label_replace(\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod),\n \"pod_name\",\n \"$1\", \n \"pod\", \n \"(.+)\"\n ) or up * 0\n) ", + "format": "time_series", + "intervalFactor": 2, + "refId": "A" + } + ], + "title": "CPU Used", + "type": "stat" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 6, + "y": 5 + }, + "id": 11, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "expr": "sum(\n sum (kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\"}) by (pod)\n * on (pod) group_left()\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod)\n or up * 0\n) ", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + } + ], + "title": "Memory Request", + "type": "stat" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 9, + "y": 5 + }, + "id": 18, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "expr": "sum(\n label_replace(\n sum (container_memory_working_set_bytes{pod_name!=\"\",container!=\"POD\",container!=\"\"}) by (pod_name),\n \"pod\",\n \"$1\", \n \"pod_name\", \n \"(.+)\")\n * on (pod) group_left()\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod)\n or up * 0\n)", + "format": "time_series", + "instant": true, + "intervalFactor": 1, + "refId": "A" + } + ], + "title": "Memory Usage", + "type": "stat" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 5 + }, + "id": 22, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "expr": "sum(\n max(kube_persistentvolumeclaim_info) by (persistentvolumeclaim, storageclass)\n * on (persistentvolumeclaim) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim)\n * on (persistentvolumeclaim) group_left(label_app)\n max(kube_persistentvolumeclaim_labels{label_$label=~\"$label_value\"}) by (persistentvolumeclaim) or up * 0\n) \n", + "format": "time_series", + "instant": true, + "intervalFactor": 1, + "refId": "A" + } + ], + "title": "Storage Request", + "type": "stat" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 9 + }, + "hiddenSeries": false, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(\n label_replace(\n sum (kube_pod_container_resource_limits{resource=\"cpu\", unit=\"core\"}) by (pod, container)\n * on (pod) group_left()\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod,container),\n \"container_name\",\n \"$1\", \n \"container\", \n \"(.+)\"\n )\n) \n", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "limit", + "refId": "C" + }, + { + "expr": "sum(\n label_replace(\n sum (kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\"}) by (pod, container)\n * on (pod) group_left()\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod,container),\n \"container_name\",\n \"$1\", \n \"container\", \n \"(.+)\"\n )\n) \n", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "request", + "refId": "B" + }, + { + "expr": "sum(\n label_replace(\n sum (rate (container_cpu_usage_seconds_total{image!=\"\",container!=\"POD\",container!=\"\"}[10m])) by (container,pod),\n \"pod\", \n \"$1\", \n \"pod_name\", \n \"(.+)\"\n )\n * on (pod) group_left (label_$label)\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod,container)\n)\n", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "usage", + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "CPU Usage vs Requests vs Limits", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 9 + }, + "hiddenSeries": false, + "id": 23, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(\n label_replace(\n sum (kube_pod_container_resource_limits{resource=\"memory\", unit=\"byte\"}) by (pod, container)\n * on (pod) group_left()\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod,container),\n \"container_name\",\n \"$1\", \n \"container\", \n \"(.+)\"\n )\n) \n", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "limit", + "refId": "C" + }, + { + "expr": "sum(\n label_replace(\n sum (kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\"}) by (pod, container)\n * on (pod) group_left()\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod,container),\n \"container_name\",\n \"$1\", \n \"container\", \n \"(.+)\"\n )\n) \n", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "request", + "refId": "B" + }, + { + "expr": "sum(\n label_replace(\n sum (container_memory_working_set_bytes{container!=\"\",container!=\"POD\"}) by (container,pod),\n \"pod\", \n \"$1\", \n \"pod_name\", \n \"(.+)\"\n )\n * on (pod) group_left (label_$label)\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod,container)\n)\n", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "usage", + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Memory Usage vs Requests vs Limits", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "bytes", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + ], + "refresh": false, + "schemaVersion": 34, + "style": "dark", + "tags": [ + "kubecost", + "cost", + "utilization", + "metrics" + ], + "templating": { + "list": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "filters": [], + "hide": 0, + "label": "", + "name": "Filters", + "skipUrlSync": false, + "type": "adhoc" + }, + { + "current": { + "tags": [], + "text": "app", + "value": "app" + }, + "hide": 0, + "includeAll": false, + "label": "Label", + "multi": false, + "name": "label", + "options": [ + { + "selected": true, + "text": "app", + "value": "app" + }, + { + "selected": false, + "text": "tier", + "value": "tier" + }, + { + "selected": false, + "text": "component", + "value": "component" + }, + { + "selected": false, + "text": "release", + "value": "release" + }, + { + "selected": false, + "text": "name", + "value": "name" + }, + { + "selected": false, + "text": "team", + "value": "team" + }, + { + "selected": false, + "text": "department", + "value": "department" + }, + { + "selected": false, + "text": "owner", + "value": "owner" + }, + { + "selected": false, + "text": "contact", + "value": "contact" + } + ], + "query": "app, tier, component, release, name, team, department, owner, contact", + "skipUrlSync": false, + "type": "custom" + }, + { + "allValue": ".*", + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "", + "hide": 0, + "includeAll": true, + "label": "Value", + "multi": false, + "name": "label_value", + "options": [], + "query": { + "query": "query_result(SUM(kube_pod_labels{label_$label!=\"\",namespace!=\"kube-system\"}) by (label_$label))", + "refId": "default-kubecost-label_value-Variable-Query" + }, + "refresh": 1, + "regex": "/label_$label=\\\"(.*?)(\\\")/", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": "()", + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "", + "hide": 0, + "includeAll": true, + "label": "", + "multi": false, + "name": "Deployments", + "options": [], + "query": { + "query": "label_values(deployment)", + "refId": "default-kubecost-Deployments-Variable-Query" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "Secondary", + "options": [], + "query": { + "query": "query_result(kube_pod_labels)", + "refId": "default-kubecost-Secondary-Variable-Query" + }, + "refresh": 1, + "regex": "/.+?label_([^=]*).*/", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": false, + "text": "default-kubecost", + "value": "default-kubecost" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time": { + "from": "now-24h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Label costs & utilization", + "uid": "lWMhIA-ik", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/namespace-utilization.json b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/namespace-utilization.json new file mode 100644 index 000000000..a2e60c1f2 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/namespace-utilization.json @@ -0,0 +1,1175 @@ +{ + "annotations":{ + "list":[ + { + "builtIn":1, + "datasource":"-- Grafana --", + "enable":true, + "hide":true, + "iconColor":"rgba(0, 211, 255, 1)", + "name":"Annotations & Alerts", + "type":"dashboard" + } + ] + }, + "description":"A dashboard to help with utilization and resource allocation", + "editable":true, + "gnetId":8673, + "graphTooltip":0, + "id":9, + "iteration":1553150922105, + "links":[ + + ], + "panels":[ + { + "columns":[ + { + "text":"Avg", + "value":"avg" + } + ], + "datasource":"${datasource}", + "fontSize":"100%", + "gridPos":{ + "h":9, + "w":16, + "x":0, + "y":0 + }, + "hideTimeOverride":true, + "id":73, + "links":[ + + ], + "pageSize":8, + "repeat":null, + "repeatDirection":"v", + "scroll":true, + "showHeader":true, + "sort":{ + "col":2, + "desc":false + }, + "styles":[ + { + "alias":"Pod", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#c15c17" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "link":false, + "linkTooltip":"", + "linkUrl":"", + "pattern":"pod_name", + "thresholds":[ + "30", + "80" + ], + "type":"string", + "unit":"currencyUSD" + }, + { + "alias":"RAM", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "pattern":"Value #B", + "thresholds":[ + + ], + "type":"number", + "unit":"decbytes" + }, + { + "alias":"CPU %", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #A", + "thresholds":[ + + ], + "type":"number", + "unit":"percent" + }, + { + "alias":"", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Time", + "thresholds":[ + + ], + "type":"hidden", + "unit":"short" + }, + { + "alias":"Storage", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #C", + "thresholds":[ + + ], + "type":"number", + "unit":"currencyUSD" + }, + { + "alias":"Total", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #D", + "thresholds":[ + + ], + "type":"number", + "unit":"currencyUSD" + }, + { + "alias":"CPU Utilization", + "colorMode":"value", + "colors":[ + "#bf1b00", + "rgba(50, 172, 45, 0.97)", + "#ef843c" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #E", + "thresholds":[ + "30", + "80" + ], + "type":"number", + "unit":"percent" + }, + { + "alias":"RAM Utilization", + "colorMode":"value", + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#ef843c" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #F", + "thresholds":[ + "30", + "80" + ], + "type":"number", + "unit":"percent" + } + ], + "targets":[ + { + "expr":"sum (rate (container_cpu_usage_seconds_total{namespace=\"$namespace\"}[10m])) by (pod_name) * 100", + "format":"table", + "hide":false, + "instant":true, + "interval":"", + "intervalFactor":1, + "legendFormat":"{{ pod_name }}", + "refId":"A" + }, + { + "expr":"sum (avg_over_time (container_memory_working_set_bytes{namespace=\"$namespace\", container_name!=\"POD\"}[10m])) by (pod_name)", + "format":"table", + "hide":false, + "instant":true, + "intervalFactor":1, + "legendFormat":"{{ pod_name }}", + "refId":"B" + } + ], + "timeFrom":"1M", + "timeShift":null, + "title":"Pod utilization analysis", + "transform":"table", + "transparent":false, + "type":"table" + }, + { + "columns":[ + { + "text":"Avg", + "value":"avg" + } + ], + "datasource":"${datasource}", + "fontSize":"100%", + "gridPos":{ + "h":9, + "w":8, + "x":16, + "y":0 + }, + "hideTimeOverride":true, + "id":90, + "links":[ + + ], + "pageSize":8, + "repeatDirection":"v", + "scroll":true, + "showHeader":true, + "sort":{ + "col":4, + "desc":true + }, + "styles":[ + { + "alias":"Namespace", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"namespace", + "thresholds":[ + + ], + "type":"hidden", + "unit":"short" + }, + { + "alias":"PVC Name", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"persistentvolumeclaim", + "thresholds":[ + + ], + "type":"number", + "unit":"short" + }, + { + "alias":"Storage Class", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"storageclass", + "thresholds":[ + + ], + "type":"number", + "unit":"short" + }, + { + "alias":"Size", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":1, + "mappingType":1, + "pattern":"Value", + "thresholds":[ + + ], + "type":"number", + "unit":"gbytes" + }, + { + "alias":"", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Time", + "thresholds":[ + + ], + "type":"hidden", + "unit":"short" + } + ], + "targets":[ + { + "expr":"sum (\n sum(kube_persistentvolumeclaim_info) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right (storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes{namespace=~\"$namespace\"}) by (persistentvolumeclaim, namespace)\n) by (namespace,persistentvolumeclaim,storageclass) / 1024 / 1024 /1024 ", + "format":"table", + "hide":false, + "instant":true, + "interval":"", + "intervalFactor":1, + "legendFormat":"{{ persistentvolumeclaim }}", + "refId":"A" + } + ], + "timeFrom":null, + "timeShift":null, + "title":"Persistent Volume Claims", + "transform":"table", + "transparent":false, + "type":"table" + }, + { + "aliasColors":{ + + }, + "bars":false, + "dashLength":10, + "dashes":false, + "datasource":"${datasource}", + "description":"CPU requests by pod divided by the rate of CPU usage over the last hour", + "fill":1, + "gridPos":{ + "h":9, + "w":24, + "x":0, + "y":9 + }, + "id":100, + "legend":{ + "avg":false, + "current":false, + "max":false, + "min":false, + "show":true, + "total":false, + "values":false + }, + "lines":true, + "linewidth":1, + "links":[ + + ], + "nullPointMode":"null", + "percentage":false, + "pointradius":5, + "points":false, + "renderer":"flot", + "seriesOverrides":[ + + ], + "spaceLength":10, + "stack":false, + "steppedLine":false, + "targets":[ + { + "expr":"topk(10,\n label_replace(\n sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", namespace=\"$namespace\"}) by (pod),\n \"pod_name\", \n \"$1\", \n \"pod\", \n \"(.+)\"\n ) \n/ on (pod_name) sum(rate(container_cpu_usage_seconds_total{namespace=\"$namespace\",pod_name=~\".+\"}[1h])) by (pod_name))", + "format":"time_series", + "intervalFactor":1, + "refId":"A" + } + ], + "thresholds":[ + + ], + "timeFrom":null, + "timeShift":null, + "title":"Ratio of CPU requests to usage (Top 10 pods)", + "tooltip":{ + "shared":true, + "sort":0, + "value_type":"individual" + }, + "type":"graph", + "xaxis":{ + "buckets":null, + "mode":"time", + "name":null, + "show":true, + "values":[ + + ] + }, + "yaxes":[ + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":true + }, + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":true + } + ], + "yaxis":{ + "align":false, + "alignLevel":null + } + }, + { + "aliasColors":{ + + }, + "bars":false, + "dashLength":10, + "dashes":false, + "datasource":"${datasource}", + "decimals":3, + "description":"This panel shows historical utilization as an average across all pods in this namespace. It only accounts for currently deployed pods", + "editable":true, + "error":false, + "fill":0, + "grid":{ + + }, + "gridPos":{ + "h":6, + "w":12, + "x":0, + "y":18 + }, + "height":"", + "id":94, + "isNew":true, + "legend":{ + "alignAsTable":false, + "avg":false, + "current":false, + "hideEmpty":false, + "hideZero":false, + "max":false, + "min":false, + "rightSide":false, + "show":false, + "sideWidth":null, + "sort":"current", + "sortDesc":true, + "total":false, + "values":true + }, + "lines":true, + "linewidth":2, + "links":[ + + ], + "nullPointMode":"connected", + "percentage":false, + "pointradius":5, + "points":false, + "renderer":"flot", + "seriesOverrides":[ + + ], + "spaceLength":10, + "stack":false, + "steppedLine":true, + "targets":[ + { + "expr":"sum (rate (container_cpu_usage_seconds_total{namespace=\"$namespace\"}[10m])) by (namespace)\n", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"10s", + "intervalFactor":1, + "legendFormat":"cpu utilization", + "metric":"container_cpu", + "refId":"A", + "step":10 + } + ], + "thresholds":[ + + ], + "timeFrom":"", + "timeShift":null, + "title":"Overall CPU Utilization", + "tooltip":{ + "msResolution":true, + "shared":true, + "sort":2, + "value_type":"cumulative" + }, + "type":"graph", + "xaxis":{ + "buckets":null, + "mode":"time", + "name":null, + "show":true, + "values":[ + + ] + }, + "yaxes":[ + { + "decimals":null, + "format":"percent", + "label":"", + "logBase":1, + "max":"110", + "min":"0", + "show":true + }, + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":false + } + ], + "yaxis":{ + "align":false, + "alignLevel":null + } + }, + { + "aliasColors":{ + + }, + "bars":false, + "dashLength":10, + "dashes":false, + "datasource":"${datasource}", + "decimals":2, + "description":"This panel shows historical utilization as an average across all pods in this namespace. It only accounts for currently deployed pods", + "editable":true, + "error":false, + "fill":0, + "grid":{ + + }, + "gridPos":{ + "h":6, + "w":12, + "x":12, + "y":18 + }, + "id":92, + "isNew":true, + "legend":{ + "alignAsTable":false, + "avg":false, + "current":false, + "max":false, + "min":false, + "rightSide":false, + "show":false, + "sideWidth":200, + "sort":"current", + "sortDesc":true, + "total":false, + "values":true + }, + "lines":true, + "linewidth":2, + "links":[ + + ], + "nullPointMode":"connected", + "percentage":false, + "pointradius":5, + "points":false, + "renderer":"flot", + "seriesOverrides":[ + + ], + "spaceLength":10, + "stack":false, + "steppedLine":true, + "targets":[ + { + "expr":"sum (container_memory_working_set_bytes{namespace=\"$namespace\"})\n/\nsum(node_memory_MemTotal_bytes)", + "format":"time_series", + "instant":false, + "intervalFactor":1, + "legendFormat":"mem utilization", + "refId":"B" + } + ], + "thresholds":[ + + ], + "timeFrom":"", + "timeShift":null, + "title":"Overall RAM Utilization", + "tooltip":{ + "msResolution":false, + "shared":true, + "sort":2, + "value_type":"cumulative" + }, + "type":"graph", + "xaxis":{ + "buckets":null, + "mode":"time", + "name":null, + "show":true, + "values":[ + + ] + }, + "yaxes":[ + { + "decimals":null, + "format":"percent", + "label":null, + "logBase":1, + "max":"110", + "min":"0", + "show":true + }, + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":false + } + ], + "yaxis":{ + "align":false, + "alignLevel":null + } + }, + { + "aliasColors":{ + + }, + "bars":false, + "dashLength":10, + "dashes":false, + "datasource":"${datasource}", + "decimals":2, + "description":"Traffic in and out of this namespace, as a sum of the pods within it", + "editable":true, + "error":false, + "fill":1, + "grid":{ + + }, + "gridPos":{ + "h":6, + "w":12, + "x":0, + "y":24 + }, + "height":"", + "id":96, + "isNew":true, + "legend":{ + "alignAsTable":false, + "avg":true, + "current":true, + "hideEmpty":false, + "hideZero":false, + "max":false, + "min":false, + "rightSide":false, + "show":true, + "sideWidth":null, + "sort":"current", + "sortDesc":true, + "total":false, + "values":true + }, + "lines":true, + "linewidth":2, + "links":[ + + ], + "nullPointMode":"connected", + "percentage":false, + "pointradius":5, + "points":false, + "renderer":"flot", + "seriesOverrides":[ + + ], + "spaceLength":10, + "stack":false, + "steppedLine":false, + "targets":[ + { + "expr":"sum (rate (container_network_receive_bytes_total{namespace=\"$namespace\"}[10m])) by (namespace)", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"", + "intervalFactor":1, + "legendFormat":"<- in", + "metric":"container_cpu", + "refId":"A", + "step":10 + }, + { + "expr":"- sum (rate (container_network_transmit_bytes_total{namespace=\"$namespace\"}[10m])) by (namespace)", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"", + "intervalFactor":1, + "legendFormat":"-> out", + "refId":"B" + } + ], + "thresholds":[ + + ], + "timeFrom":"", + "timeShift":null, + "title":"Network IO", + "tooltip":{ + "msResolution":true, + "shared":true, + "sort":2, + "value_type":"cumulative" + }, + "type":"graph", + "xaxis":{ + "buckets":null, + "mode":"time", + "name":null, + "show":true, + "values":[ + + ] + }, + "yaxes":[ + { + "format":"Bps", + "label":"", + "logBase":1, + "max":null, + "min":null, + "show":true + }, + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":false + } + ], + "yaxis":{ + "align":false, + "alignLevel":null + } + }, + { + "aliasColors":{ + + }, + "bars":false, + "dashLength":10, + "dashes":false, + "datasource":"${datasource}", + "decimals":2, + "description":"Disk reads and writes for the namespace, as a sum of the pods within it", + "editable":true, + "error":false, + "fill":1, + "grid":{ + + }, + "gridPos":{ + "h":6, + "w":12, + "x":12, + "y":24 + }, + "height":"", + "id":98, + "isNew":true, + "legend":{ + "alignAsTable":false, + "avg":true, + "current":true, + "hideEmpty":false, + "hideZero":false, + "max":false, + "min":false, + "rightSide":false, + "show":true, + "sideWidth":null, + "sort":"current", + "sortDesc":true, + "total":false, + "values":true + }, + "lines":true, + "linewidth":2, + "links":[ + + ], + "nullPointMode":"connected", + "percentage":false, + "pointradius":5, + "points":false, + "renderer":"flot", + "seriesOverrides":[ + + ], + "spaceLength":10, + "stack":false, + "steppedLine":false, + "targets":[ + { + "expr":"sum (rate (container_fs_writes_bytes_total{namespace=\"$namespace\"}[10m])) by (namespace)", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"", + "intervalFactor":1, + "legendFormat":"<- write", + "metric":"container_cpu", + "refId":"A", + "step":10 + }, + { + "expr":"- sum (rate (container_fs_reads_bytes_total{namespace=\"$namespace\"}[10m])) by (namespace)", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"", + "intervalFactor":1, + "legendFormat":"-> read", + "refId":"B" + } + ], + "thresholds":[ + + ], + "timeFrom":"", + "timeShift":null, + "title":"Disk IO", + "tooltip":{ + "msResolution":true, + "shared":true, + "sort":2, + "value_type":"cumulative" + }, + "type":"graph", + "xaxis":{ + "buckets":null, + "mode":"time", + "name":null, + "show":true, + "values":[ + + ] + }, + "yaxes":[ + { + "format":"Bps", + "label":"", + "logBase":1, + "max":null, + "min":null, + "show":true + }, + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":false + } + ], + "yaxis":{ + "align":false, + "alignLevel":null + } + } + ], + "refresh":"10s", + "schemaVersion":16, + "style":"dark", + "tags":[ + "cost", + "utilization", + "metrics" + ], + "templating":{ + "list":[ + { + "current":{ + "text":"23.06", + "value":"23.06" + }, + "hide":0, + "label":"CPU", + "name":"costcpu", + "options":[ + { + "text":"23.06", + "value":"23.06" + } + ], + "query":"23.06", + "skipUrlSync":false, + "type":"constant" + }, + { + "current":{ + "text":"7.28", + "value":"7.28" + }, + "hide":0, + "label":"PE CPU", + "name":"costpcpu", + "options":[ + { + "text":"7.28", + "value":"7.28" + } + ], + "query":"7.28", + "skipUrlSync":false, + "type":"constant" + }, + { + "current":{ + "text":"3.25", + "value":"3.25" + }, + "hide":0, + "label":"RAM", + "name":"costram", + "options":[ + { + "text":"3.25", + "value":"3.25" + } + ], + "query":"3.25", + "skipUrlSync":false, + "type":"constant" + }, + { + "current":{ + "text":"0.6862", + "value":"0.6862" + }, + "hide":0, + "label":"PE RAM", + "name":"costpram", + "options":[ + { + "text":"0.6862", + "value":"0.6862" + } + ], + "query":"0.6862", + "skipUrlSync":false, + "type":"constant" + }, + { + "current":{ + "text":"0.04", + "value":"0.04" + }, + "hide":0, + "label":"Storage", + "name":"costStorageStandard", + "options":[ + { + "text":"0.04", + "value":"0.04" + } + ], + "query":"0.04", + "skipUrlSync":false, + "type":"constant" + }, + { + "current":{ + "text":".17", + "value":".17" + }, + "hide":0, + "label":"SSD", + "name":"costStorageSSD", + "options":[ + { + "text":".17", + "value":".17" + } + ], + "query":".17", + "skipUrlSync":false, + "type":"constant" + }, + { + "current":{ + "text":"30", + "value":"30" + }, + "hide":0, + "label":"Disc.", + "name":"costDiscount", + "options":[ + { + "text":"30", + "value":"30" + } + ], + "query":"30", + "skipUrlSync":false, + "type":"constant" + }, + { + "allValue":null, + "current":{ + "text":"kube-system", + "value":"kube-system" + }, + "datasource":"${datasource}", + "hide":0, + "includeAll":false, + "label":"NS", + "multi":false, + "name":"namespace", + "options":[ + + ], + "query":"query_result(sum(kube_namespace_created{namespace!=\"\"}) by (namespace))", + "refresh":1, + "regex":"/namespace=\\\"(.*?)(\\\")/", + "skipUrlSync":false, + "sort":0, + "tagValuesQuery":"", + "tags":[ + + ], + "tagsQuery":"", + "type":"query", + "useTags":false + }, + { + "datasource":"${datasource}", + "filters":[ + + ], + "hide":0, + "label":"", + "name":"Filters", + "skipUrlSync":false, + "type":"adhoc" + }, + { + "current": { + "selected": true, + "text": "default-kubecost", + "value": "default-kubecost" + }, + "error": null, + "hide": 0, + "includeAll": false, + "label": null, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time":{ + "from":"now-15m", + "to":"now" + }, + "timepicker":{ + "hidden":false, + "refresh_intervals":[ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options":[ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone":"browser", + "title":"Namespace utilization metrics", + "uid":"at-cost-analysis-namespace2", + "version":1 +} diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/network-cloud-services.json b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/network-cloud-services.json new file mode 100644 index 000000000..2729b6ca7 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/network-cloud-services.json @@ -0,0 +1,408 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Most used metrics when troubleshooting applications", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 14, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 11, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum(\n rate(kubecost_pod_network_ingress_bytes_total\n {namespace=~\"$namespace\", pod_name=~\"$pod\",service!=\"\",service=~\"$service\"}\n [1h]\n )\n) \nby (namespace,service) ", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "interval": "1h", + "legendFormat": "{{namespace}}/{{service}}", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "- sum(\n rate(kubecost_pod_network_egress_bytes_total\n {namespace=~\"$namespace\", pod_name=~\"$pod\",service!=\"\",service=~\"$service\"}\n [1h]\n )\n) \nby(namespace, service) ", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "interval": "1h", + "legendFormat": "{{namespace}}/{{service}}", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Kubecost Network Cloud Service by Namespace (egress is negative)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum(topk(5,\n rate(kubecost_pod_network_ingress_bytes_total\n {namespace=~\"$namespace\", pod_name=~\"$pod\",service!=\"\",service=~\"$service\"}\n [1h]\n )\n) )\nby(namespace, pod_name,service) ", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "interval": "1h", + "legendFormat": "{{namespace}}/{{pod_name}}/{{service}}", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "- sum(topk(5,\n rate(kubecost_pod_network_egress_bytes_total\n {namespace=~\"$namespace\", pod_name=~\"$pod\",service!=\"\",service=~\"$service\"}\n [1h]\n )\n) )\nby(namespace, pod_name,service) ", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "interval": "1h", + "legendFormat": "{{namespace}}/{{pod_name}}/{{service}}", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Kubecost Network Cloud Service by Pod (egress is negative)", + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 39, + "tags": [ + "utilization", + "metrics", + "kubecost" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "PBFA97CFB590B2093" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_namespace_labels,namespace)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "namespace", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kube_namespace_labels,namespace)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_pod_owner{namespace=~\"$namespace\"},pod)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "pod", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kube_pod_owner{namespace=~\"$namespace\"},pod)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "definition": "label_values(kube_pod_container_status_running{namespace=\"$namespace\"},container)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "container", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kube_pod_container_status_running{namespace=\"$namespace\"},container)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": true, + "text": "All", + "value": "$__all" + }, + "definition": "label_values(kubecost_pod_network_egress_bytes_total{namespace=~\"$namespace\"},service)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "service", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kubecost_pod_network_egress_bytes_total{namespace=~\"$namespace\"},service)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-2d", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Kubecost Network Cloud Service Metrics", + "uid": "kubecost-network-cloud-services", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/networkCosts-metrics.json b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/networkCosts-metrics.json new file mode 100644 index 000000000..79e568ccb --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/networkCosts-metrics.json @@ -0,0 +1,672 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "https://docs.kubecost.com/install-and-configure/advanced-configuration/network-costs-configuration", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 2, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 12, + "panels": [], + "title": "Network Data Transfers (negative is egress data)", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 11, + "x": 0, + "y": 1 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(kubecost_pod_network_ingress_bytes_total\n {namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", service=~\"$service\"}\n [1h]\n ))\nby($aggregation) ", + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "-sum(increase(kubecost_pod_network_egress_bytes_total\n {namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", service=~\"$service\"}\n [1h]\n ))\nby($aggregation) ", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "All Data", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 13, + "x": 11, + "y": 1 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(kubecost_pod_network_ingress_bytes_total\n {internet=\"true\", namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", service=~\"$service\"}\n [1h]\n))\nby($aggregation) ", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "- sum(increase(kubecost_pod_network_egress_bytes_total\n {internet=\"true\", namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", service=~\"$service\"}\n [1h]))\nby($aggregation) ", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "Internet Data", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Cross region and cross zone subnets must be defined via the configMap. \nSee: \n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 11, + "x": 0, + "y": 15 + }, + "id": 9, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(kubecost_pod_network_ingress_bytes_total\n {internet=\"false\",namespace=~\"$namespace\",cluster_id=~\"$cluster\",pod_name=~\"$pod_name\", sameRegion=\"false\",sameZone=\"false\", service=~\"$service\"}\n [1h]))\nby($aggregation)", + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "- sum(increase(kubecost_pod_network_egress_bytes_total\n {internet=\"false\", namespace=~\"$namespace\",cluster_id=~\"$cluster\",pod_name=~\"$pod_name\",sameRegion=\"false\", sameZone=\"false\", service=~\"$service\"}\n [1h]))\nby($aggregation) ", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "Cross Region Data", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Cross region and cross zone subnets must be defined via the configMap. \nSee: \n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 13, + "x": 11, + "y": 15 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(kubecost_pod_network_ingress_bytes_total\n {internet=\"false\", namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", sameRegion=\"true\", sameZone=\"false\", service=~\"$service\"}\n [1h]))\nby($aggregation)", + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "- sum(increase(kubecost_pod_network_egress_bytes_total\n {internet=\"false\", namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", sameRegion=\"true\", sameZone=\"false\", service=~\"$service\"}\n [1h]))\nby($aggregation)", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "Cross Zone Data", + "type": "timeseries" + } + ], + "refresh": "", + "revision": 1, + "schemaVersion": 39, + "tags": [ + "utilization", + "metrics", + "kubecost" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "PBFA97CFB590B2093" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": false, + "text": "namespace", + "value": "namespace" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "aggregation", + "options": [ + { + "selected": false, + "text": "cluster_id", + "value": "cluster_id" + }, + { + "selected": true, + "text": "namespace", + "value": "namespace" + }, + { + "selected": false, + "text": "pod_name", + "value": "pod_name" + } + ], + "query": "cluster_id, namespace, pod_name", + "queryValue": "", + "skipUrlSync": false, + "type": "custom" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(cluster_id)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "cluster", + "options": [], + "query": { + "query": "label_values(cluster_id)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": true, + "text": "kubecost", + "value": "kubecost" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_namespace_labels{cluster_id=~\"$cluster\"}, namespace) ", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "namespace", + "options": [], + "query": { + "query": "label_values(kube_namespace_labels{cluster_id=~\"$cluster\"}, namespace) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kubecost_pod_network_egress_bytes_total{cluster_id=~\"$cluster\", namespace=~\"$namespace\"},pod_name)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "pod_name", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kubecost_pod_network_egress_bytes_total{cluster_id=~\"$cluster\", namespace=~\"$namespace\"},pod_name)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "filters": [], + "hide": 0, + "name": "filter", + "skipUrlSync": false, + "type": "adhoc" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "definition": "label_values(kubecost_pod_network_egress_bytes_total{namespace=~\"$namespace\"},service)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "service", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kubecost_pod_network_egress_bytes_total{namespace=~\"$namespace\"},service)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-7d", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Kubecost Network Costs Metrics", + "uid": "kubecost-networkCosts-metrics", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/node-utilization.json b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/node-utilization.json new file mode 100644 index 000000000..dc03cc074 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/node-utilization.json @@ -0,0 +1,1389 @@ +{ + "annotations":{ + "list":[ + { + "builtIn":1, + "datasource":"-- Grafana --", + "enable":true, + "hide":true, + "iconColor":"rgba(0, 211, 255, 1)", + "name":"Annotations & Alerts", + "type":"dashboard" + } + ] + }, + "editable":true, + "gnetId":null, + "graphTooltip":0, + "id":6, + "iteration":1557245882378, + "links":[ + + ], + "panels":[ + { + "cacheTimeout":null, + "colorBackground":false, + "colorValue":false, + "colors":[ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource":"${datasource}", + "format":"percentunit", + "gauge":{ + "maxValue":100, + "minValue":0, + "show":true, + "thresholdLabels":false, + "thresholdMarkers":true + }, + "gridPos":{ + "h":7, + "w":8, + "x":0, + "y":0 + }, + "id":2, + "interval":null, + "links":[ + + ], + "mappingType":1, + "mappingTypes":[ + { + "name":"value to text", + "value":1 + }, + { + "name":"range to text", + "value":2 + } + ], + "maxDataPoints":100, + "nullPointMode":"connected", + "nullText":null, + "postfix":"", + "postfixFontSize":"50%", + "prefix":"", + "prefixFontSize":"50%", + "rangeMaps":[ + { + "from":"null", + "text":"N/A", + "to":"null" + } + ], + "sparkline":{ + "fillColor":"rgba(31, 118, 189, 0.18)", + "full":false, + "lineColor":"rgb(31, 120, 193)", + "show":false + }, + "tableColumn":"", + "targets":[ + { + "expr":"sum(irate(container_cpu_usage_seconds_total{id=\"/\",instance=\"$node\"}[10m]))", + "format":"time_series", + "intervalFactor":1, + "refId":"A" + } + ], + "thresholds":"", + "title":"CPU Usage", + "type":"singlestat", + "valueFontSize":"80%", + "valueMaps":[ + { + "op":"=", + "text":"N/A", + "value":"null" + } + ], + "valueName":"avg" + }, + { + "cacheTimeout":null, + "colorBackground":false, + "colorValue":false, + "colors":[ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource":"${datasource}", + "format":"percentunit", + "gauge":{ + "maxValue":100, + "minValue":0, + "show":true, + "thresholdLabels":false, + "thresholdMarkers":true + }, + "gridPos":{ + "h":7, + "w":8, + "x":8, + "y":0 + }, + "id":3, + "interval":null, + "links":[ + + ], + "mappingType":1, + "mappingTypes":[ + { + "name":"value to text", + "value":1 + }, + { + "name":"range to text", + "value":2 + } + ], + "maxDataPoints":100, + "nullPointMode":"connected", + "nullText":null, + "postfix":"", + "postfixFontSize":"50%", + "prefix":"", + "prefixFontSize":"50%", + "rangeMaps":[ + { + "from":"null", + "text":"N/A", + "to":"null" + } + ], + "sparkline":{ + "fillColor":"rgba(31, 118, 189, 0.18)", + "full":false, + "lineColor":"rgb(31, 120, 193)", + "show":false + }, + "tableColumn":"", + "targets":[ + { + "expr":"SUM(container_memory_usage_bytes{namespace!=\"\",instance=\"$node\"}) / SUM(kube_node_status_capacity{resource=\"memory\", unit=\"byte\", node=\"$node\"})", + "format":"time_series", + "intervalFactor":1, + "refId":"A" + } + ], + "thresholds":"", + "title":"Memory Usage", + "type":"singlestat", + "valueFontSize":"80%", + "valueMaps":[ + { + "op":"=", + "text":"N/A", + "value":"null" + } + ], + "valueName":"avg" + }, + { + "cacheTimeout":null, + "colorBackground":false, + "colorValue":false, + "colors":[ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource":"${datasource}", + "format":"percentunit", + "gauge":{ + "maxValue":100, + "minValue":0, + "show":true, + "thresholdLabels":false, + "thresholdMarkers":true + }, + "gridPos":{ + "h":7, + "w":8, + "x":16, + "y":0 + }, + "id":4, + "interval":null, + "links":[ + + ], + "mappingType":1, + "mappingTypes":[ + { + "name":"value to text", + "value":1 + }, + { + "name":"range to text", + "value":2 + } + ], + "maxDataPoints":100, + "nullPointMode":"connected", + "nullText":null, + "postfix":"", + "postfixFontSize":"50%", + "prefix":"", + "prefixFontSize":"50%", + "rangeMaps":[ + { + "from":"null", + "text":"N/A", + "to":"null" + } + ], + "sparkline":{ + "fillColor":"rgba(31, 118, 189, 0.18)", + "full":false, + "lineColor":"rgb(31, 120, 193)", + "show":false + }, + "tableColumn":"", + "targets":[ + { + "expr":"sum(container_fs_usage_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\",instance=\"$node\"}) /\nsum(container_fs_limit_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\",instance=\"$node\"})", + "format":"time_series", + "intervalFactor":1, + "refId":"A" + } + ], + "thresholds":"", + "title":"Storage Usage", + "type":"singlestat", + "valueFontSize":"80%", + "valueMaps":[ + { + "op":"=", + "text":"N/A", + "value":"null" + } + ], + "valueName":"avg" + }, + { + "columns":[ + { + "text":"Avg", + "value":"avg" + } + ], + "datasource":"${datasource}", + "fontSize":"100%", + "gridPos":{ + "h":8, + "w":16, + "x":0, + "y":7 + }, + "hideTimeOverride":true, + "id":21, + "links":[ + + ], + "pageSize":8, + "repeat":null, + "repeatDirection":"v", + "scroll":true, + "showHeader":true, + "sort":{ + "col":4, + "desc":true + }, + "styles":[ + { + "alias":"Pod", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#c15c17" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "link":false, + "linkTooltip":"", + "linkUrl":"", + "pattern":"pod_name", + "thresholds":[ + "30", + "80" + ], + "type":"string", + "unit":"currencyUSD" + }, + { + "alias":"", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Time", + "thresholds":[ + + ], + "type":"hidden", + "unit":"short" + }, + { + "alias":"CPU Usage", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #C", + "thresholds":[ + + ], + "type":"number", + "unit":"short" + }, + { + "alias":"CPU Request", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #A", + "thresholds":[ + + ], + "type":"number", + "unit":"short" + }, + { + "alias":"CPU Limit", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #B", + "thresholds":[ + + ], + "type":"number", + "unit":"short" + }, + { + "alias":"Mem Usage", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #D", + "thresholds":[ + + ], + "type":"number", + "unit":"bytes" + }, + { + "alias":"Mem Request", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #E", + "thresholds":[ + + ], + "type":"number", + "unit":"bytes" + }, + { + "alias":"Mem Limit", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #F", + "thresholds":[ + + ], + "type":"number", + "unit":"bytes" + } + ], + "targets":[ + { + "expr":"sum(rate(container_cpu_usage_seconds_total{container_name!=\"\",container_name!=\"POD\",pod_name!=\"\",instance=\"$node\"}[24h])) by (pod_name)", + "format":"table", + "instant":true, + "intervalFactor":1, + "refId":"C" + }, + { + "expr":"sum(label_replace(\nsum(avg_over_time(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", container!=\"\",container!=\"POD\",node=\"$node\"}[24h])) by (pod), \n\"pod_name\",\"$1\",\"pod\",\"(.+)\")\nor up * 0\n) by (pod_name)", + "format":"table", + "instant":true, + "intervalFactor":1, + "refId":"A" + }, + { + "expr":"sum(avg_over_time(container_memory_usage_bytes{container_name!=\"\",container_name!=\"POD\",pod_name!=\"\",instance=\"$node\"}[24h])) by (pod_name)\n", + "format":"table", + "instant":true, + "intervalFactor":1, + "refId":"D" + }, + { + "expr":"sum(label_replace(label_replace(\nsum(avg_over_time(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", container!=\"\",container!=\"POD\",node=\"$node\"}[24h])) by (pod),\n\"container_name\",\"$1\",\"container\",\"(.+)\"), \"pod_name\",\"$1\",\"pod\",\"(.+)\")\nor up * 0\n) by (pod_name)\n", + "format":"table", + "instant":true, + "intervalFactor":1, + "refId":"E" + } + ], + "timeFrom":"1M", + "timeShift":null, + "title":"Current pods", + "transform":"table", + "transparent":false, + "type":"table" + }, + { + "cacheTimeout":null, + "colorBackground":false, + "colorValue":false, + "colors":[ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource":"${datasource}", + "decimals":0, + "format":"none", + "gauge":{ + "maxValue":100, + "minValue":0, + "show":false, + "thresholdLabels":false, + "thresholdMarkers":true + }, + "gridPos":{ + "h":4, + "w":4, + "x":16, + "y":7 + }, + "id":8, + "interval":null, + "links":[ + + ], + "mappingType":1, + "mappingTypes":[ + { + "name":"value to text", + "value":1 + }, + { + "name":"range to text", + "value":2 + } + ], + "maxDataPoints":100, + "nullPointMode":"connected", + "nullText":null, + "postfix":"", + "postfixFontSize":"50%", + "prefix":"", + "prefixFontSize":"50%", + "rangeMaps":[ + { + "from":"null", + "text":"N/A", + "to":"null" + } + ], + "sparkline":{ + "fillColor":"rgba(31, 118, 189, 0.18)", + "full":false, + "lineColor":"rgb(31, 120, 193)", + "show":false + }, + "tableColumn":"", + "targets":[ + { + "expr":"sum(\n count(avg_over_time(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", container!=\"\",container!=\"POD\",node=\"$node\"}[24h])) by (pod)\n * on (pod) group_right()\n sum(kube_pod_container_status_running) by (pod)\n)", + "format":"time_series", + "instant":true, + "intervalFactor":1, + "refId":"A" + } + ], + "thresholds":"", + "title":"Pods Running", + "type":"singlestat", + "valueFontSize":"80%", + "valueMaps":[ + { + "op":"=", + "text":"N/A", + "value":"null" + } + ], + "valueName":"current" + }, + { + "cacheTimeout":null, + "colorBackground":false, + "colorValue":false, + "colors":[ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource":"${datasource}", + "format":"bytes", + "gauge":{ + "maxValue":100, + "minValue":0, + "show":false, + "thresholdLabels":false, + "thresholdMarkers":true + }, + "gridPos":{ + "h":4, + "w":4, + "x":20, + "y":7 + }, + "id":18, + "interval":null, + "links":[ + + ], + "mappingType":1, + "mappingTypes":[ + { + "name":"value to text", + "value":1 + }, + { + "name":"range to text", + "value":2 + } + ], + "maxDataPoints":100, + "nullPointMode":"connected", + "nullText":null, + "postfix":"", + "postfixFontSize":"50%", + "prefix":"", + "prefixFontSize":"50%", + "rangeMaps":[ + { + "from":"null", + "text":"N/A", + "to":"null" + } + ], + "sparkline":{ + "fillColor":"rgba(31, 118, 189, 0.18)", + "full":false, + "lineColor":"rgb(31, 120, 193)", + "show":false + }, + "tableColumn":"", + "targets":[ + { + "expr":"sum(container_fs_limit_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\",instance=\"$node\"})", + "format":"time_series", + "intervalFactor":1, + "refId":"A" + } + ], + "thresholds":"", + "title":"Storage Capacity", + "type":"singlestat", + "valueFontSize":"80%", + "valueMaps":[ + { + "op":"=", + "text":"N/A", + "value":"null" + } + ], + "valueName":"current" + }, + { + "cacheTimeout":null, + "colorBackground":false, + "colorValue":false, + "colors":[ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource":"${datasource}", + "format":"none", + "gauge":{ + "maxValue":100, + "minValue":0, + "show":false, + "thresholdLabels":false, + "thresholdMarkers":true + }, + "gridPos":{ + "h":4, + "w":4, + "x":16, + "y":11 + }, + "id":9, + "interval":null, + "links":[ + + ], + "mappingType":1, + "mappingTypes":[ + { + "name":"value to text", + "value":1 + }, + { + "name":"range to text", + "value":2 + } + ], + "maxDataPoints":100, + "nullPointMode":"connected", + "nullText":null, + "postfix":"", + "postfixFontSize":"50%", + "prefix":"", + "prefixFontSize":"50%", + "rangeMaps":[ + { + "from":"null", + "text":"N/A", + "to":"null" + } + ], + "sparkline":{ + "fillColor":"rgba(31, 118, 189, 0.18)", + "full":false, + "lineColor":"rgb(31, 120, 193)", + "show":false + }, + "tableColumn":"", + "targets":[ + { + "expr":"kube_node_status_capacity{resource=\"cpu\", unit=\"core\", node=\"$node\"}", + "format":"time_series", + "intervalFactor":1, + "refId":"A" + } + ], + "thresholds":"", + "title":"CPU Capacity", + "type":"singlestat", + "valueFontSize":"80%", + "valueMaps":[ + { + "op":"=", + "text":"N/A", + "value":"null" + } + ], + "valueName":"avg" + }, + { + "cacheTimeout":null, + "colorBackground":false, + "colorValue":false, + "colors":[ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource":"${datasource}", + "format":"bytes", + "gauge":{ + "maxValue":100, + "minValue":0, + "show":false, + "thresholdLabels":false, + "thresholdMarkers":true + }, + "gridPos":{ + "h":4, + "w":4, + "x":20, + "y":11 + }, + "id":19, + "interval":null, + "links":[ + + ], + "mappingType":1, + "mappingTypes":[ + { + "name":"value to text", + "value":1 + }, + { + "name":"range to text", + "value":2 + } + ], + "maxDataPoints":100, + "nullPointMode":"connected", + "nullText":null, + "postfix":"", + "postfixFontSize":"50%", + "prefix":"", + "prefixFontSize":"50%", + "rangeMaps":[ + { + "from":"null", + "text":"N/A", + "to":"null" + } + ], + "sparkline":{ + "fillColor":"rgba(31, 118, 189, 0.18)", + "full":false, + "lineColor":"rgb(31, 120, 193)", + "show":false + }, + "tableColumn":"", + "targets":[ + { + "expr":"kube_node_status_capacity{resource=\"memory\", unit=\"byte\", node=\"$node\"}", + "format":"time_series", + "intervalFactor":1, + "refId":"A" + } + ], + "thresholds":"", + "title":"RAM Capacity", + "type":"singlestat", + "valueFontSize":"80%", + "valueMaps":[ + { + "op":"=", + "text":"N/A", + "value":"null" + } + ], + "valueName":"current" + }, + { + "aliasColors":{ + + }, + "bars":false, + "dashLength":10, + "dashes":false, + "datasource":"${datasource}", + "decimals":3, + "description":"This panel shows historical utilization for the node.", + "editable":true, + "error":false, + "fill":0, + "grid":{ + + }, + "gridPos":{ + "h":6, + "w":12, + "x":0, + "y":15 + }, + "height":"", + "id":11, + "isNew":true, + "legend":{ + "alignAsTable":false, + "avg":false, + "current":false, + "hideEmpty":false, + "hideZero":false, + "max":false, + "min":false, + "rightSide":false, + "show":false, + "sideWidth":null, + "sort":"current", + "sortDesc":true, + "total":false, + "values":true + }, + "lines":true, + "linewidth":2, + "links":[ + + ], + "nullPointMode":"connected", + "percentage":false, + "pointradius":5, + "points":false, + "renderer":"flot", + "seriesOverrides":[ + + ], + "spaceLength":10, + "stack":false, + "steppedLine":true, + "targets":[ + { + "expr":"sum(irate(container_cpu_usage_seconds_total{id=\"/\",instance=\"$node\"}[10m]))", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"10s", + "intervalFactor":1, + "legendFormat":"cpu utilization", + "metric":"container_cpu", + "refId":"A", + "step":10 + } + ], + "thresholds":[ + + ], + "timeFrom":"", + "timeShift":null, + "title":"CPU Utilization", + "tooltip":{ + "msResolution":true, + "shared":true, + "sort":2, + "value_type":"cumulative" + }, + "type":"graph", + "xaxis":{ + "buckets":null, + "mode":"time", + "name":null, + "show":true, + "values":[ + + ] + }, + "yaxes":[ + { + "decimals":null, + "format":"percentunit", + "label":"", + "logBase":1, + "max":"1.1", + "min":"0", + "show":true + }, + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":false + } + ], + "yaxis":{ + "align":false, + "alignLevel":null + } + }, + { + "aliasColors":{ + + }, + "bars":false, + "dashLength":10, + "dashes":false, + "datasource":"${datasource}", + "decimals":2, + "description":"This panel shows historical utilization for the node.", + "editable":true, + "error":false, + "fill":0, + "grid":{ + + }, + "gridPos":{ + "h":6, + "w":12, + "x":12, + "y":15 + }, + "id":13, + "isNew":true, + "legend":{ + "alignAsTable":false, + "avg":false, + "current":false, + "max":false, + "min":false, + "rightSide":false, + "show":false, + "sideWidth":200, + "sort":"current", + "sortDesc":true, + "total":false, + "values":true + }, + "lines":true, + "linewidth":2, + "links":[ + + ], + "nullPointMode":"connected", + "percentage":false, + "pointradius":5, + "points":false, + "renderer":"flot", + "seriesOverrides":[ + + ], + "spaceLength":10, + "stack":false, + "steppedLine":true, + "targets":[ + { + "expr":"SUM(container_memory_usage_bytes{namespace!=\"\",instance=\"$node\"}) / SUM(kube_node_status_capacity{resource=\"memory\", unit=\"byte\", node=\"$node\"})", + "format":"time_series", + "instant":false, + "interval":"10s", + "intervalFactor":1, + "legendFormat":"ram utilization", + "metric":"container_memory_usage:sort_desc", + "refId":"A", + "step":10 + } + ], + "thresholds":[ + + ], + "timeFrom":"", + "timeShift":null, + "title":"RAM Utilization", + "tooltip":{ + "msResolution":false, + "shared":true, + "sort":2, + "value_type":"cumulative" + }, + "type":"graph", + "xaxis":{ + "buckets":null, + "mode":"time", + "name":null, + "show":true, + "values":[ + + ] + }, + "yaxes":[ + { + "decimals":null, + "format":"percentunit", + "label":null, + "logBase":1, + "max":"1.1", + "min":"0", + "show":true + }, + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":false + } + ], + "yaxis":{ + "align":false, + "alignLevel":null + } + }, + { + "aliasColors":{ + + }, + "bars":false, + "dashLength":10, + "dashes":false, + "datasource":"${datasource}", + "decimals":2, + "description":"Traffic in and out of this namespace, as a sum of the pods within it", + "editable":true, + "error":false, + "fill":1, + "grid":{ + + }, + "gridPos":{ + "h":6, + "w":12, + "x":0, + "y":21 + }, + "height":"", + "id":15, + "isNew":true, + "legend":{ + "alignAsTable":false, + "avg":true, + "current":true, + "hideEmpty":false, + "hideZero":false, + "max":false, + "min":false, + "rightSide":false, + "show":true, + "sideWidth":null, + "sort":"current", + "sortDesc":true, + "total":false, + "values":true + }, + "lines":true, + "linewidth":2, + "links":[ + + ], + "nullPointMode":"connected", + "percentage":false, + "pointradius":5, + "points":false, + "renderer":"flot", + "seriesOverrides":[ + + ], + "spaceLength":10, + "stack":false, + "steppedLine":false, + "targets":[ + { + "expr":"sum (rate (container_network_receive_bytes_total{instance=\"$node\"}[10m]))", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"", + "intervalFactor":1, + "legendFormat":"<- in", + "metric":"container_cpu", + "refId":"A", + "step":10 + }, + { + "expr":"- sum (rate (container_network_transmit_bytes_total{instance=\"$node\"}[10m]))", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"", + "intervalFactor":1, + "legendFormat":"-> out", + "refId":"B" + } + ], + "thresholds":[ + + ], + "timeFrom":"", + "timeShift":null, + "title":"Network IO", + "tooltip":{ + "msResolution":true, + "shared":true, + "sort":2, + "value_type":"cumulative" + }, + "type":"graph", + "xaxis":{ + "buckets":null, + "mode":"time", + "name":null, + "show":true, + "values":[ + + ] + }, + "yaxes":[ + { + "format":"Bps", + "label":"", + "logBase":1, + "max":null, + "min":null, + "show":true + }, + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":false + } + ], + "yaxis":{ + "align":false, + "alignLevel":null + } + }, + { + "aliasColors":{ + + }, + "bars":false, + "dashLength":10, + "dashes":false, + "datasource":"${datasource}", + "decimals":2, + "description":"Disk reads and writes for the namespace, as a sum of the pods within it", + "editable":true, + "error":false, + "fill":1, + "grid":{ + + }, + "gridPos":{ + "h":6, + "w":12, + "x":12, + "y":21 + }, + "height":"", + "id":17, + "isNew":true, + "legend":{ + "alignAsTable":false, + "avg":true, + "current":true, + "hideEmpty":false, + "hideZero":false, + "max":false, + "min":false, + "rightSide":false, + "show":true, + "sideWidth":null, + "sort":"current", + "sortDesc":true, + "total":false, + "values":true + }, + "lines":true, + "linewidth":2, + "links":[ + + ], + "nullPointMode":"connected", + "percentage":false, + "pointradius":5, + "points":false, + "renderer":"flot", + "seriesOverrides":[ + + ], + "spaceLength":10, + "stack":false, + "steppedLine":false, + "targets":[ + { + "expr":"sum (rate (container_fs_writes_bytes_total{instance=\"$node\"}[10m]))", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"", + "intervalFactor":1, + "legendFormat":"<- write", + "metric":"container_cpu", + "refId":"A", + "step":10 + }, + { + "expr":"- sum (rate (container_fs_reads_bytes_total{instance=\"$node\"}[10m]))", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"", + "intervalFactor":1, + "legendFormat":"-> read", + "refId":"B" + } + ], + "thresholds":[ + + ], + "timeFrom":"", + "timeShift":null, + "title":"Disk IO", + "tooltip":{ + "msResolution":true, + "shared":true, + "sort":2, + "value_type":"cumulative" + }, + "type":"graph", + "xaxis":{ + "buckets":null, + "mode":"time", + "name":null, + "show":true, + "values":[ + + ] + }, + "yaxes":[ + { + "format":"Bps", + "label":"", + "logBase":1, + "max":null, + "min":null, + "show":true + }, + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":false + } + ], + "yaxis":{ + "align":false, + "alignLevel":null + } + } + ], + "schemaVersion":16, + "style":"dark", + "tags":[ + "cost", + "utilization", + "metrics" + ], + "templating":{ + "list":[ + { + "allValue":null, + "current":{ + "text":"ip-172-20-44-170.us-east-2.compute.internal", + "value":"ip-172-20-44-170.us-east-2.compute.internal" + }, + "datasource":"${datasource}", + "hide":0, + "includeAll":false, + "label":null, + "multi":false, + "name":"node", + "options":[ + + ], + "query":"query_result(kube_node_labels)", + "refresh":1, + "regex":"/node=\\\"(.*?)(\\\")/", + "skipUrlSync":false, + "sort":0, + "tagValuesQuery":"", + "tags":[ + + ], + "tagsQuery":"", + "type":"query", + "useTags":false + }, + { + "current": { + "selected": true, + "text": "default-kubecost", + "value": "default-kubecost" + }, + "error": null, + "hide": 0, + "includeAll": false, + "label": null, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time":{ + "from":"now-6h", + "to":"now" + }, + "timepicker":{ + "refresh_intervals":[ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options":[ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone":"", + "title":"Node utilization metrics", + "uid":"NUQW37Lmk", + "version":1 +} diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/pod-utilization-multi-cluster.json b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/pod-utilization-multi-cluster.json new file mode 100644 index 000000000..3054b9fdd --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/pod-utilization-multi-cluster.json @@ -0,0 +1,788 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": 9063, + "graphTooltip": 0, + "id": 4, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "description": "Maximum CPU Core Usage vs avg Requested", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 94, + "links": [], + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "editorMode": "code", + "expr": "max(irate(container_cpu_usage_seconds_total\r\n {cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\", container=~\"$container\", container!=\"POD\",container!=\"\"}\r\n [$__rate_interval])) \r\n by (cluster_id, namespace, pod, container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}/{{container}} (usage max)", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "editorMode": "code", + "exemplar": true, + "expr": "avg(kube_pod_container_resource_requests\r\n {cluster_id=\"$cluster\",resource=\"cpu\",unit=\"core\",namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$container\",container!=\"POD\"}\r\n ) \r\nby (cluster_id,namespace,pod,container)", + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}/{{container}} (requested)", + "range": true, + "refId": "B" + } + ], + "timeFrom": "", + "title": "CPU Core Usage vs Requested", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "description": "Max memory used vs avg requested", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 96, + "links": [], + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "asc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "editorMode": "code", + "expr": "max(max_over_time(container_memory_working_set_bytes\r\n {namespace=~\"$namespace\",pod=~\"$pod\",cluster_id=\"$cluster\",container=~\"$container\",container!=\"POD\",container!=\"\"}\r\n [$__rate_interval])) \r\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}/{{container}} (usage max)", + "metric": "container_cpu", + "refId": "MEMORY_USAGE", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "editorMode": "code", + "expr": "avg(kube_pod_container_resource_requests\n {resource=\"memory\",unit=\"byte\",cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\", container=~\"$container\",container!=\"POD\"}\n )\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}/{{container}} (requested)", + "refId": "MEMORY_REQUESTED" + } + ], + "timeFrom": "", + "title": "Memory Usage vs Requested", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "description": "Network traffic by pod", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 7 + }, + "id": 95, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "editorMode": "code", + "expr": "sum(irate(container_network_receive_bytes_total\n {cluster_id=~\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\"}\n [$__rate_interval])) \nby (cluster_id, namespace, pod)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}<- in", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "editorMode": "code", + "expr": "- sum(irate(container_network_transmit_bytes_total\n {cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\"}\n [$__rate_interval])) \nby (cluster_id, namespace, pod)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}-> out", + "refId": "B" + } + ], + "timeFrom": "", + "title": "Network IO", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "description": "Disk read writes", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 7 + }, + "id": 97, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "editorMode": "code", + "expr": "sum(irate(container_fs_writes_bytes_total\r\n {cluster_id=\"$cluster\",namespace=~\"$namespace\",container!=\"POD\",pod!=\"\",pod=~\"$pod\",container=~\"$container\"}\r\n [$__rate_interval])) \r\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{pod}}/{{container}}<- write", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "editorMode": "code", + "expr": "- sum(irate(container_fs_reads_bytes_total\r\n {cluster_id=\"$cluster\",namespace=~\"$namespace\",container!=\"POD\",pod!=\"\",pod=~\"$pod\",container=~\"$container\"}\r\n [$__rate_interval])) \r\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{pod}}/{{container}}-> read", + "refId": "B" + } + ], + "timeFrom": "", + "title": "Disk IO", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "description": "This graph shows the % of periods where a pod is being throttled. Values range from 0-100", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 14 + }, + "id": 99, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "editorMode": "code", + "expr": "100\n * sum by(cluster_id, namespace, pod, container) (increase(container_cpu_cfs_throttled_periods_total{container!=\"\",cluster_id=\"$cluster\", namespace=~\"$namespace\", pod=~\"$pod\", container=~\"$container\", container!=\"POD\"}[$__rate_interval]))\n / sum by(cluster_id,namespace,pod,container) (increase(container_cpu_cfs_periods_total{container!=\"\",cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$container\",container!=\"POD\"}[$__rate_interval]))", + "format": "time_series", + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "", + "refId": "B" + } + ], + "timeFrom": "", + "title": "CPU throttle percent", + "type": "timeseries" + } + ], + "refresh": "", + "revision": 1, + "schemaVersion": 39, + "tags": [ + "utilization", + "metrics", + "kubecost" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "CostManagement", + "value": "CostManagement" + }, + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "definition": "label_values(cluster_id)", + "hide": 0, + "includeAll": false, + "multi": false, + "name": "cluster", + "options": [], + "query": { + "query": "label_values(cluster_id)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "kubecost", + "value": "kubecost" + }, + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "definition": "label_values(kube_namespace_labels{cluster_id=\"$cluster\"}, namespace) ", + "hide": 0, + "includeAll": true, + "label": "", + "multi": false, + "name": "namespace", + "options": [], + "query": { + "query": "label_values(kube_namespace_labels{cluster_id=\"$cluster\"}, namespace) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "definition": "label_values(kube_pod_labels{cluster_id=\"$cluster\",namespace=~\"$namespace\"}, pod) ", + "hide": 0, + "includeAll": true, + "label": "pod", + "multi": false, + "name": "pod", + "options": [], + "query": { + "query": "label_values(kube_pod_labels{cluster_id=\"$cluster\",namespace=~\"$namespace\"}, pod) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "definition": "label_values(container_memory_working_set_bytes{cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\", container!=\"POD\"}, container) ", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "container", + "options": [], + "query": { + "query": "label_values(container_memory_working_set_bytes{cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\", container!=\"POD\"}, container) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-2d", + "to": "now" + }, + "timepicker": { + "hidden": false, + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "browser", + "title": "Pod utilization metrics (multi-cluster)", + "uid": "at-cost-analysis-pod-multicluster", + "version": 2, + "weekStart": "" +} diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/pod-utilization.json b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/pod-utilization.json new file mode 100644 index 000000000..f037af45e --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/pod-utilization.json @@ -0,0 +1,757 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": 9063, + "graphTooltip": 0, + "id": 11, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Maximum CPU Core Usage vs Requested", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 94, + "links": [], + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "max(irate(\r\n container_cpu_usage_seconds_total\r\n {namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$container\",container!=\"POD\",container!=\"\"}\r\n [$__rate_interval])) \r\n by (cluster_id, namespace, pod, container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{pod}}/{{container}} (usage max)", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "exemplar": true, + "expr": "avg(kube_pod_container_resource_requests\r\n {resource=\"cpu\",unit=\"core\",namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$container\",container!=\"POD\"}\r\n ) \r\nby (cluster_id, namespace, pod, container)", + "legendFormat": "{{cluster_id}} {{pod}}/{{container}} (avg requested)", + "range": true, + "refId": "B" + } + ], + "timeFrom": "", + "title": "CPU Core Usage vs Requested", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Max Memory usage vs avg requested", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 96, + "links": [], + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "asc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "max(max_over_time(\r\n container_memory_working_set_bytes\r\n {namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$container\",container!=\"POD\",container!=\"\"}\r\n [$__rate_interval])) \r\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{pod}}/{{container}} (usage max)", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "avg(\n kube_pod_container_resource_requests\n {resource=\"memory\",unit=\"byte\",namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$container\", container!=\"POD\"}\n )\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{pod}}/{{container}} (avg requested)", + "refId": "B" + } + ], + "timeFrom": "", + "title": "Memory Usage vs Requested", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Network traffic by pod", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 7 + }, + "id": 95, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(irate(container_network_receive_bytes_total\n {namespace=~\"$namespace\",pod=~\"$pod\"}\n [$__rate_interval])) \nby (cluster_id, namespace, pod)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}<- in", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "- sum(irate(container_network_transmit_bytes_total\n {namespace=~\"$namespace\",pod=~\"$pod\"}\n [$__rate_interval])) \nby (cluster_id, namespace, pod)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}-> out", + "refId": "B" + } + ], + "timeFrom": "", + "title": "Network IO", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Disk read writes", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 7 + }, + "id": 97, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(irate(container_fs_writes_bytes_total\r\n {container!=\"POD\",pod!=\"\",pod=~\"$pod\",container=~\"$container\"}\r\n [$__rate_interval])) \r\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{pod}}/{{container}}<- write", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "- sum(irate(container_fs_reads_bytes_total\r\n {container!=\"POD\",pod!=\"\",pod=~\"$pod\",container=~\"$container\"}\r\n [$__rate_interval])) \r\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{pod}}/{{container}}-> read", + "refId": "B" + } + ], + "timeFrom": "", + "title": "Disk IO", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "This graph shows the % of periods where a pod is being throttled. Values range from 0-100", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 14 + }, + "id": 99, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "100\n * sum by(cluster_id, namespace, pod, container) (increase(container_cpu_cfs_throttled_periods_total{container!=\"\", namespace=~\"$namespace\", pod=~\"$pod\", container=~\"$container\", container!=\"POD\"}[$__rate_interval]))\n / sum by(cluster_id, namespace, pod, container) (increase(container_cpu_cfs_periods_total{container!=\"\", namespace=~\"$namespace\", pod=~\"$pod\", container=~\"$container\", container!=\"POD\"}[$__rate_interval]))", + "format": "time_series", + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "__auto", + "refId": "B" + } + ], + "timeFrom": "", + "title": "CPU throttle percent", + "type": "timeseries" + } + ], + "refresh": "", + "revision": 1, + "schemaVersion": 38, + "style": "dark", + "tags": [ + "kubecost", + "utilization", + "metrics" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "Prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "uid": "${datasource}" + }, + "definition": "label_values(kube_namespace_labels, namespace) ", + "hide": 0, + "includeAll": true, + "label": "", + "multi": false, + "name": "namespace", + "options": [], + "query": { + "query": "label_values(kube_namespace_labels, namespace) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "uid": "${datasource}" + }, + "definition": "label_values(kube_pod_labels{namespace=~\"$namespace\"}, pod) ", + "hide": 0, + "includeAll": true, + "label": "pod", + "multi": false, + "name": "pod", + "options": [], + "query": { + "query": "label_values(kube_pod_labels{namespace=~\"$namespace\"}, pod) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(container_memory_working_set_bytes{namespace=~\"$namespace\",pod=~\"$pod\", container!=\"POD\"}, container) ", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "container", + "options": [], + "query": { + "query": "label_values(container_memory_working_set_bytes{namespace=~\"$namespace\",pod=~\"$pod\", container!=\"POD\"}, container) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-2d", + "to": "now" + }, + "timepicker": { + "hidden": false, + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "browser", + "title": "Pod utilization metrics", + "uid": "at-cost-analysis-pod", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/prom-benchmark.json b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/prom-benchmark.json new file mode 100644 index 000000000..ff054acc2 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/prom-benchmark.json @@ -0,0 +1,5691 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Metrics useful for benchmarking and loadtesting Prometheus itself. Designed primarily for Prometheus 2.17.x.", + "editable": true, + "gnetId": 12054, + "graphTooltip": 1, + "id": 9, + "iteration": 1603144824023, + "links": [], + "panels": [ + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 49, + "panels": [], + "title": "Basics", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 1 + }, + "hiddenSeries": false, + "id": 40, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_build_info{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{version}} - {{revision}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Prometheus Version", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 1 + }, + "hiddenSeries": false, + "id": 72, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "time() - process_start_time_seconds{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Age", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Uptime", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "dtdurations", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 1 + }, + "hiddenSeries": false, + "id": 107, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "time() - prometheus_config_last_reload_success_timestamp_seconds{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Age", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Last Successful Config Reload", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "dtdurations", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 46, + "panels": [], + "title": "Ingestion", + "type": "row" + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833", + "Time series": "#70dbed" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 9 + }, + "hiddenSeries": false, + "id": 3, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_tsdb_head_series{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Time series", + "metric": "prometheus_local_storage_memory_series", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Head Time series", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 9 + }, + "hiddenSeries": false, + "id": 26, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_tsdb_head_active_appenders{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Head Appenders", + "metric": "prometheus_local_storage_memory_series", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Head Active Appenders", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "samples/s": "#e5a8e2" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 9 + }, + "hiddenSeries": false, + "id": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_head_samples_appended_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "samples/s", + "metric": "prometheus_local_storage_ingested_samples_total", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Samples Appended/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833", + "To persist": "#9AC48A" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 16 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "/Max.*/", + "fill": 0 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_tsdb_head_chunks{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Chunks", + "metric": "prometheus_local_storage_memory_chunks", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Head Chunks", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 16 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_head_chunks_created_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Created", + "metric": "prometheus_local_storage_chunk_ops_total", + "refId": "A", + "step": 10 + }, + { + "expr": "rate(prometheus_tsdb_head_chunks_removed_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Removed", + "metric": "prometheus_local_storage_chunk_ops_total", + "refId": "B", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Head Chunks/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ops", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833", + "Removed": "#e5ac0e" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 16 + }, + "hiddenSeries": false, + "id": 25, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_tsdb_isolation_high_watermark{job=\"prometheus\",instance=\"$Prometheus:9090\"} - prometheus_tsdb_isolation_low_watermark{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Difference", + "metric": "prometheus_local_storage_chunk_ops_total", + "refId": "B", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Isolation Watermarks", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 23 + }, + "id": 52, + "panels": [], + "title": "Compaction", + "type": "row" + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max": "#447ebc", + "Max chunks": "#052B51", + "Max to persist": "#3F6833", + "Min": "#447ebc", + "Now": "#7eb26d" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 24 + }, + "hiddenSeries": false, + "id": 28, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "Max", + "fillBelowTo": "Min", + "lines": false + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_tsdb_head_min_time{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Min", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + }, + { + "expr": "time() * 1000", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "Now", + "refId": "C" + }, + { + "expr": "prometheus_tsdb_head_max_time{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Max", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "B", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Head Time Range", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "dateTimeAsIso", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 24 + }, + "hiddenSeries": false, + "id": 29, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_head_gc_duration_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "GC Time/s", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Head GC Time/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 24 + }, + "hiddenSeries": false, + "id": 14, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "Queue length", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_tsdb_blocks_loaded{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Blocks Loaded", + "metric": "prometheus_local_storage_indexing_batch_sizes_sum", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Blocks Loaded", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Failed Compactions": "#bf1b00", + "Failed Reloads": "#bf1b00", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 31 + }, + "hiddenSeries": false, + "id": 30, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_reloads_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Reloads", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "TSDB Reloads/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Failed Compactions": "#bf1b00", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 31 + }, + "hiddenSeries": false, + "id": 31, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_wal_fsync_duration_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m]) / rate(prometheus_tsdb_wal_fsync_duration_seconds_count{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Fsync Latency", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + }, + { + "expr": "rate(prometheus_tsdb_wal_truncate_duration_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m]) / rate(prometheus_tsdb_wal_trunacte_duration_seconds_count{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Truncate Latency", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "B", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "WAL Fsync&Truncate Latency", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Failed Compactions": "#bf1b00", + "Max chunks": "#052B51", + "Max to persist": "#3F6833", + "{instance=\"demo.robustperception.io:9090\",job=\"prometheus\"}": "#bf1b00" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 31 + }, + "hiddenSeries": false, + "id": 32, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_wal_corruptions_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "WAL Corruptions", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + }, + { + "expr": "rate(prometheus_tsdb_reloads_failures_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Reload Failures", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "B", + "step": 10 + }, + { + "expr": "rate(prometheus_tsdb_head_series_not_found{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Head Series Not Found", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "C", + "step": 10 + }, + { + "expr": "rate(prometheus_tsdb_compactions_failed_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Compaction Failures", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "D", + "step": 10 + }, + { + "expr": "rate(prometheus_tsdb_retention_cutoffs_failures_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Retention Cutoff Failures", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "E", + "step": 10 + }, + { + "expr": "rate(prometheus_tsdb_checkpoint_creations_failed_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "WAL Checkpoint Creation Failures", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "F", + "step": 10 + }, + { + "expr": "rate(prometheus_tsdb_checkpoint_deletions_failed_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "WAL Checkpoint Deletion Failures", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "G", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "TSDB Problems/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Failed Compactions": "#bf1b00", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 38 + }, + "hiddenSeries": false, + "id": 19, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_compactions_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Compactions", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Compactions/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 38 + }, + "hiddenSeries": false, + "id": 33, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_compaction_duration_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Compaction Time/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Allocated bytes": "#F9BA8F", + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833", + "RSS": "#890F02" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 38 + }, + "hiddenSeries": false, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_time_retentions_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Time Cutoffs", + "metric": "last", + "refId": "A", + "step": 10 + }, + { + "expr": "rate(prometheus_tsdb_size_retentions_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Size Cutoffs", + "metric": "last", + "refId": "B", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Retention Cutoffs/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 45 + }, + "hiddenSeries": false, + "id": 27, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_compaction_chunk_range_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m]) / rate(prometheus_tsdb_compaction_chunk_range_seconds_count{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Chunk Time Range", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "First Compaction, Avg Chunk Time Range", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "dtdurationms", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 45 + }, + "hiddenSeries": false, + "id": 35, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_compaction_chunk_size_bytes_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m]) / rate(prometheus_tsdb_compaction_chunk_samples_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Bytes/Sample", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "First Compaction, Avg Bytes/Sample", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 45 + }, + "hiddenSeries": false, + "id": 34, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_compaction_chunk_samples_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m]) / rate(prometheus_tsdb_compaction_chunk_samples_count{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Chunk Samples", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "First Compaction, Avg Chunk Samples", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 52 + }, + "id": 55, + "panels": [], + "title": "Resource Usage", + "type": "row" + }, + { + "aliasColors": { + "Allocated bytes": "#7EB26D", + "Allocated bytes - 1m max": "#BF1B00", + "Allocated bytes - 1m min": "#BF1B00", + "Allocated bytes - 5m max": "#BF1B00", + "Allocated bytes - 5m min": "#BF1B00", + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833", + "RSS": "#447EBC" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "decimals": null, + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 53 + }, + "hiddenSeries": false, + "id": 6, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "/-/", + "fill": 0 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "process_resident_memory_bytes{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "intervalFactor": 2, + "legendFormat": "RSS", + "metric": "process_resident_memory_bytes", + "refId": "B", + "step": 10 + }, + { + "expr": "prometheus_local_storage_target_heap_size_bytes{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "intervalFactor": 2, + "legendFormat": "Target heap size", + "metric": "go_memstats_alloc_bytes", + "refId": "D", + "step": 10 + }, + { + "expr": "go_memstats_next_gc_bytes{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "intervalFactor": 2, + "legendFormat": "Next GC", + "metric": "go_memstats_next_gc_bytes", + "refId": "C", + "step": 10 + }, + { + "expr": "go_memstats_alloc_bytes{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "intervalFactor": 2, + "legendFormat": "Allocated", + "metric": "go_memstats_alloc_bytes", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Memory", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Allocated bytes": "#F9BA8F", + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833", + "RSS": "#890F02" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 53 + }, + "hiddenSeries": false, + "id": 7, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(go_memstats_alloc_bytes_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Allocated Bytes/s", + "metric": "go_memstats_alloc_bytes", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Allocations", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "decimals": 2, + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 53 + }, + "hiddenSeries": false, + "id": 9, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "hideEmpty": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "irate(process_cpu_seconds_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m])", + "intervalFactor": 2, + "legendFormat": "Irate", + "metric": "prometheus_local_storage_ingested_samples_total", + "refId": "A", + "step": 10 + }, + { + "expr": "rate(process_cpu_seconds_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[5m])", + "intervalFactor": 2, + "legendFormat": "5m rate", + "metric": "prometheus_local_storage_ingested_samples_total", + "refId": "B", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "CPU", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [ + "avg" + ] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "decimals": 2, + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 60 + }, + "hiddenSeries": false, + "id": 70, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "hideEmpty": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_tsdb_symbol_table_size_bytes{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "RAM Used", + "metric": "prometheus_local_storage_ingested_samples_total", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Symbol Tables Size", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [ + "avg" + ] + }, + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "decimals": 2, + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 60 + }, + "hiddenSeries": false, + "id": 71, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "hideEmpty": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_tsdb_storage_blocks_bytes_total{job=\"prometheus\",instance=\"$Prometheus:9090\"} or prometheus_tsdb_storage_blocks_bytes{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Disk Used", + "metric": "prometheus_local_storage_ingested_samples_total", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Block Size", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [ + "avg" + ] + }, + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Max": "#e24d42", + "Open": "#508642" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 60 + }, + "hiddenSeries": false, + "id": 41, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "process_max_fds{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Max", + "refId": "A" + }, + { + "expr": "process_open_fds{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Open", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "File Descriptors", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 67 + }, + "id": 91, + "panels": [], + "title": "Service Discovery", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 68 + }, + "hiddenSeries": false, + "id": 42, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_sd_discovered_targets{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{name}}-{{config}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Discovered Targets", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 68 + }, + "hiddenSeries": false, + "id": 96, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_sd_updates_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[5m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{name}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Sent Updates/s", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 68 + }, + "hiddenSeries": false, + "id": 97, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_sd_received_updates_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[5m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{name}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Received Updates/s", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 75 + }, + "id": 99, + "panels": [], + "title": "Scraping", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 76 + }, + "hiddenSeries": false, + "id": 105, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_target_interval_length_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m]) / rate(prometheus_target_interval_length_seconds_count{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{interval}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Scrape Interval", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 76 + }, + "hiddenSeries": false, + "id": 104, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_target_scrapes_exceeded_sample_limit_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "Exceeded Sample Limit", + "refId": "A" + }, + { + "expr": "rate(prometheus_target_scrapes_sample_duplicate_timestamp_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "Duplicate Timestamp", + "refId": "C" + }, + { + "expr": "rate(prometheus_target_scrapes_sample_out_of_bounds_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "Out Of Bounds ", + "refId": "D" + }, + { + "expr": "rate(prometheus_target_scrapes_sample_out_of_order_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "Out of Order", + "refId": "E" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Scrape Problems/s", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 76 + }, + "hiddenSeries": false, + "id": 95, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_target_metadata_cache_bytes{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{scrape_job}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Metadata Cache Size", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 83 + }, + "id": 63, + "panels": [], + "title": "Query Engine", + "type": "row" + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "description": "Time spent in each mode, per second", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 84 + }, + "hiddenSeries": false, + "id": 24, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_engine_query_duration_seconds_sum{job=\"prometheus\",}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{slice}}", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Query engine timings/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 84 + }, + "hiddenSeries": false, + "id": 22, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_rule_group_iterations_missed_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m]) ", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Rule group missed", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "B", + "step": 10 + }, + { + "expr": "rate(prometheus_rule_evaluation_failures_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Rule evals failed", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "C", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Rule group evaulation problems/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 84 + }, + "hiddenSeries": false, + "id": 23, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_rule_group_duration_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Rule evaluation duration", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Evaluation time of rule groups/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": true, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 91 + }, + "id": 77, + "panels": [ + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 92 + }, + "hiddenSeries": false, + "id": 86, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_notifications_sent_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{alertmanager}}", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Notification Sent/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 92 + }, + "hiddenSeries": false, + "id": 87, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_notifications_errors_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m]) / rate(prometheus_notifications_sent_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{alertmanager}}", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Notification Error Ratio", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 92 + }, + "hiddenSeries": false, + "id": 81, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_notifications_latency_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m]) / rate(prometheus_notifications_latency_seconds_count{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{alertmanager}}", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Notification Latency", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 99 + }, + "hiddenSeries": false, + "id": 85, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_notifications_alertmanagers_discovered{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Alertmanagers", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Alertmanagers Discovered", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 99 + }, + "hiddenSeries": false, + "id": 89, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_notifications_dropped_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Dropped", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Notifications Dropped/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 99 + }, + "hiddenSeries": false, + "id": 88, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_notifications_queue_length{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Pending", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + }, + { + "expr": "prometheus_notifications_queue_capacity{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Max", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "B", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Notification Queue", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "title": "Notification", + "type": "row" + }, + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 92 + }, + "id": 58, + "panels": [], + "title": "HTTP Server", + "type": "row" + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "description": "", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 93 + }, + "hiddenSeries": false, + "id": 38, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_http_request_duration_seconds_count{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{handler}}", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "HTTP requests/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "description": "", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 93 + }, + "hiddenSeries": false, + "id": 37, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_http_request_duration_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m]) / rate(prometheus_http_request_duration_seconds_count{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{handler}}", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "HTTP request latency", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "description": "", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 93 + }, + "hiddenSeries": false, + "id": 36, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_http_request_duration_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{handler}}", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Time spent in HTTP requests/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 100 + }, + "id": 61, + "panels": [], + "repeat": "RuleGroup", + "scopedVars": { + "RuleGroup": { + "selected": false, + "text": "/etc/config/rules;CPU", + "value": "/etc/config/rules;CPU" + } + }, + "title": "Rule Group: $RuleGroup", + "type": "row" + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Interval": "#890f02", + "Last Duration": "#f9934e", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 101 + }, + "hiddenSeries": false, + "id": 43, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "repeatDirection": "h", + "scopedVars": { + "RuleGroup": { + "selected": false, + "text": "/etc/config/rules;CPU", + "value": "/etc/config/rules;CPU" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_rule_group_interval_seconds{job=\"prometheus\",instance=\"$Prometheus:9090\",rule_group=~\"$RuleGroup\"}\n", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Interval", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + }, + { + "expr": "prometheus_rule_group_last_duration_seconds{job=\"prometheus\",instance=\"$Prometheus:9090\",rule_group=~\"$RuleGroup\"}\n", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Last Duration", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "B", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$RuleGroup: Duration", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Interval": "#890f02", + "Last Duration": "#f9934e", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 101 + }, + "hiddenSeries": false, + "id": 66, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "scopedVars": { + "RuleGroup": { + "selected": false, + "text": "/etc/config/rules;CPU", + "value": "/etc/config/rules;CPU" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_rule_group_rules{job=\"prometheus\",instance=\"$Prometheus:9090\",rule_group=~\"$RuleGroup\"}\n", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Rules", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$RuleGroup: Rules", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 0, + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 108 + }, + "id": 108, + "panels": [], + "repeat": null, + "repeatIteration": 1603144824023, + "repeatPanelId": 61, + "scopedVars": { + "RuleGroup": { + "selected": false, + "text": "/etc/config/rules;Savings", + "value": "/etc/config/rules;Savings" + } + }, + "title": "Rule Group: $RuleGroup", + "type": "row" + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Interval": "#890f02", + "Last Duration": "#f9934e", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 109 + }, + "hiddenSeries": false, + "id": 109, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "repeatDirection": "h", + "repeatIteration": 1603144824023, + "repeatPanelId": 43, + "repeatedByRow": true, + "scopedVars": { + "RuleGroup": { + "selected": false, + "text": "/etc/config/rules;Savings", + "value": "/etc/config/rules;Savings" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_rule_group_interval_seconds{job=\"prometheus\",instance=\"$Prometheus:9090\",rule_group=~\"$RuleGroup\"}\n", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Interval", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + }, + { + "expr": "prometheus_rule_group_last_duration_seconds{job=\"prometheus\",instance=\"$Prometheus:9090\",rule_group=~\"$RuleGroup\"}\n", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Last Duration", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "B", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$RuleGroup: Duration", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Interval": "#890f02", + "Last Duration": "#f9934e", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 109 + }, + "hiddenSeries": false, + "id": 110, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "repeatIteration": 1603144824023, + "repeatPanelId": 66, + "repeatedByRow": true, + "scopedVars": { + "RuleGroup": { + "selected": false, + "text": "/etc/config/rules;Savings", + "value": "/etc/config/rules;Savings" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_rule_group_rules{job=\"prometheus\",instance=\"$Prometheus:9090\",rule_group=~\"$RuleGroup\"}\n", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Rules", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$RuleGroup: Rules", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 0, + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": false, + "schemaVersion": 26, + "style": "dark", + "tags": [ + "kubecost" + ], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "selected": false, + "text": "localhost", + "value": "localhost" + }, + "datasource": "${datasource}", + "definition": "", + "hide": 0, + "includeAll": false, + "label": null, + "multi": false, + "name": "Prometheus", + "options": [], + "query": "query_result(up{job=\"prometheus\"} == 1)", + "refresh": 2, + "regex": ".*instance=\"([^\"]+):9090\".*", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": null, + "tags": [], + "tagsQuery": null, + "type": "query", + "useTags": false + }, + { + "allValue": null, + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "${datasource}", + "definition": "", + "hide": 2, + "includeAll": true, + "label": null, + "multi": false, + "name": "RuleGroup", + "options": [], + "query": "prometheus_rule_group_last_duration_seconds{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "refresh": 2, + "regex": ".*rule_group=\"(.*?)\".*", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": true, + "text": "default-kubecost", + "value": "default-kubecost" + }, + "error": null, + "hide": 0, + "includeAll": false, + "label": null, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time": { + "from": "now-24h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "utc", + "title": "Prometheus Benchmark - 2.17.x", + "uid": "L0HBvojWz", + "version": 4 +} diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/workload-metrics-aggregator.json b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/workload-metrics-aggregator.json new file mode 100644 index 000000000..660358905 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/workload-metrics-aggregator.json @@ -0,0 +1,988 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Most used metrics when troubleshooting applications", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 7, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "topk(5,sum(container_memory_working_set_bytes{container=\"aggregator\",namespace=~\"$namespace\"} ) by (namespace,pod,container))", + "instant": false, + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "A" + } + ], + "title": "Top Memory Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "topk(5, (\r\n sum(rate(container_cpu_usage_seconds_total{image!=\"\",namespace=~\"$namespace\",container=\"aggregator\"}[$__rate_interval])) by (namespace,pod,container)\r\n )\r\n)", + "instant": false, + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "A" + } + ], + "title": "Top CPU Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 9, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum(topk(5,\n rate(kubecost_pod_network_ingress_bytes_total\n {namespace=~\"$namespace\", pod_name=~\"$pod\"}\n [$__rate_interval]\n )\n) )\nby(namespace, pod_name) ", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{namespace}}/{{pod_name}}", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "- sum(topk(5,\n rate(kubecost_pod_network_egress_bytes_total\n {namespace=~\"$namespace\", pod_name=~\"$pod\"}\n [$__rate_interval]\n )\n) )\nby(namespace, pod_name) ", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{namespace}}/{{pod_name}}", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Kubecost Top Network (egress is negative)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": 3600000, + "lineInterpolation": "smooth", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(topk(5,rate(container_network_receive_bytes_total{namespace=~\"$namespace\"}[$__rate_interval]))) by (namespace,pod) ", + "hide": false, + "instant": false, + "legendFormat": "{{namespace}}/{{pod}}", + "range": true, + "refId": "receive" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "-sum(topk(5,rate(container_network_transmit_bytes_total{namespace=~\"$namespace\"}[$__rate_interval]))) by (namespace,pod) ", + "hide": false, + "instant": false, + "legendFormat": "{{namespace}}/{{pod}}", + "range": true, + "refId": "transmit" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "container_network_transmit_bytes_total{}", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Top Network (transmit is negative)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": 3600000, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 7, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "\n sum(rate(container_fs_writes_bytes_total\n {container=\"aggregator\",namespace=~\"$namespace\",image!=\"\"}\n [$__rate_interval]))\n by (namespace,pod,container)\n>0 ", + "hide": false, + "instant": false, + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "storage_write" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "-(sum\r\n (rate(container_fs_reads_bytes_total\r\n {container=\"aggregator\",namespace=~\"$namespace\",image!=\"\"}\r\n [$__rate_interval])) \r\nby (namespace,pod,container) \r\n) <0", + "hide": false, + "instant": false, + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "storage_read" + } + ], + "title": "Storage (read is negative)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "This may work depending on the CRI", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(kubelet_volume_stats_available_bytes{namespace=~\"$namespace\"}) by (namespace,persistentvolumeclaim)", + "hide": false, + "instant": false, + "legendFormat": "{{namespace}}/{{persistentvolumeclaim}}", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(kube_persistentvolume_capacity_bytes) by (persistentvolume)", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Storage ", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 82, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 24 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(increase(kube_pod_container_status_restarts_total{namespace=~\"$namespace\",pod=~\".+-aggregator-0\"}[1h])) by (namespace,container)>0", + "instant": false, + "interval": "1h", + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "A" + } + ], + "title": "Pod restarts per hour", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 24 + }, + "id": 11, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "kubecost_read_db_size", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "kubecost_read_db_size" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(kubecost_write_db_size)", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "kubecost_write_db_size" + } + ], + "title": "Aggregator DB Size", + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 39, + "tags": [ + "kubecost", + "utilization", + "metrics" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "PBFA97CFB590B2093" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": true, + "text": "kubecost", + "value": "kubecost" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_namespace_labels,namespace)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "namespace", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kube_namespace_labels,namespace)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_pod_owner{namespace=~\"$namespace\"},pod)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "pod", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kube_pod_owner{namespace=~\"$namespace\"},pod)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-3h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Workload Metrics - Aggregator", + "uid": "kubecost-aggregator-metrics", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/workload-metrics.json b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/workload-metrics.json new file mode 100644 index 000000000..248afc134 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/grafana-dashboards/workload-metrics.json @@ -0,0 +1,893 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Most used metrics when troubleshooting applications", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 2, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "topk(5,sum(container_memory_working_set_bytes{container=~\"$container\",pod=~\"$pod\",container!=\"\",namespace=~\"$namespace\"} ) by (namespace,pod,container))", + "instant": false, + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "A" + } + ], + "title": "Top Memory Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "topk(5, (\r\n sum(rate(container_cpu_usage_seconds_total{image!=\"\",namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$container\"}[10m])) by (namespace,pod,container)\r\n )\r\n)", + "instant": false, + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "A" + } + ], + "title": "Top CPU Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 9, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum(topk(5,\n rate(kubecost_pod_network_ingress_bytes_total\n {namespace=~\"$namespace\", pod_name=~\"$pod\"}\n [$__rate_interval]\n )\n) )\nby(namespace, pod_name) ", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{namespace}}/{{pod_name}}", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "- sum(topk(5,\n rate(kubecost_pod_network_egress_bytes_total\n {namespace=~\"$namespace\", pod_name=~\"$pod\"}\n [$__rate_interval]\n )\n) )\nby(namespace, pod_name) ", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{namespace}}/{{pod_name}}", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Kubecost Top Network (egress is negative)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": 3600000, + "lineInterpolation": "smooth", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(topk(5,rate(container_network_receive_bytes_total{pod=~\"$pod\",namespace=~\"$namespace\"}[$__rate_interval]))) by (namespace,pod) ", + "hide": false, + "instant": false, + "legendFormat": "{{namespace}}/{{pod}}", + "range": true, + "refId": "receive" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "-sum(topk(5,rate(container_network_transmit_bytes_total{pod=~\"$pod\",namespace=~\"$namespace\"}[$__rate_interval]))) by (namespace,pod) ", + "hide": false, + "instant": false, + "legendFormat": "{{namespace}}/{{pod}}", + "range": true, + "refId": "transmit" + } + ], + "title": "Top Network (transmit is negative)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": 3600000, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 7, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "\n sum(rate(container_fs_writes_bytes_total\n {pod=~\"$pod\",namespace=~\"$namespace\",image!=\"\"}\n [$__rate_interval]))\n by (namespace,pod,container)\n>0 ", + "hide": false, + "instant": false, + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "storage_write" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "-(sum\r\n (rate(container_fs_reads_bytes_total\r\n {pod=~\"$pod\",namespace=~\"$namespace\",image!=\"\"}\r\n [$__rate_interval])) \r\nby (namespace,pod,container) \r\n) <0", + "hide": false, + "instant": false, + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "storage_read" + } + ], + "title": "Storage (read is negative)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "This may work depending on the CRI", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(kubelet_volume_stats_available_bytes{namespace=~\"$namespace\"}) by (namespace,persistentvolumeclaim)", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(kube_persistentvolume_capacity_bytes) by (persistentvolume)", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Storage ", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 82, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 24 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(increase(kube_pod_container_status_restarts_total{namespace=~\"$namespace\",pod=~\"$pod\"}[1h])) by (namespace,container)>0", + "instant": false, + "interval": "1h", + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "A" + } + ], + "title": "Pod restarts per hour", + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 39, + "tags": [ + "kubecost", + "utilization", + "metrics" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "PBFA97CFB590B2093" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": true, + "text": "kubecost", + "value": "kubecost" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_namespace_labels,namespace)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "namespace", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kube_namespace_labels,namespace)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_pod_owner{namespace=~\"$namespace\"},pod)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "pod", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kube_pod_owner{namespace=~\"$namespace\"},pod)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "definition": "label_values({namespace=~\"$namespace\", pod=~\"$pod\"},container)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "container", + "options": [], + "query": { + "qryType": 1, + "query": "label_values({namespace=~\"$namespace\", pod=~\"$pod\"},container)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Workload Metrics", + "uid": "kubecost-workload-metrics", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/questions.yaml b/charts/kubecost/cost-analyzer/2.3.4/questions.yaml new file mode 100644 index 000000000..7717d04dc --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/questions.yaml @@ -0,0 +1,187 @@ +questions: + # General Settings + - variable: kubecostProductConfigs.clusterName + label: Cluster Name + description: "Used for display in the cost-analyzer UI (Can be renamed in the UI)" + type: string + required: true + default: "" + group: General Settings + - variable: persistentVolume.enabled + label: Enable Persistent Volume for CostAnalyzer + description: "If true, Kubecost will create a Persistent Volume Claim for product config data" + type: boolean + default: false + show_subquestion_if: true + group: "General Settings" + subquestions: + - variable: persistentVolume.size + label: CostAnalyzer Persistent Volume Size + type: string + default: "0.2Gi" + # Amazon EKS + - variable: AmazonEKS.enabled + label: Amazon EKS cluster + description: "If true, Kubecost will be installed with the images and helm chart from https://gallery.ecr.aws/kubecost/" + type: boolean + default: false + show_subquestion_if: true + group: General Settings + subquestions: + - variable: kubecostFrontend.image + label: Kubecost frontend image for Amazon EKS + description: "Use this image for the Amazon EKS cluster: public.ecr.aws/kubecost/frontend" + type: string + default: "" + - variable: kubecostModel.image + label: Kubecost cost-model image for Amazon EKS + description: "Use this image for the Amazon EKS cluster: public.ecr.aws/kubecost/cost-model" + type: string + default: "" + - variable: prometheus.server.image.repository + label: Kubecost Prometheus image for Amazon EKS + description: "Use this image for the Amazon EKS cluster: public.ecr.aws/kubecost/prometheus" + type: string + default: "" + - variable: prometheus.server.image.tag + label: Kubecost Prometheus image tag for Amazon EKS + type: string + default: "v2.35.0" + # Prometheus Server + - variable: global.prometheus.enabled + label: Enable Prometheus + description: If false, use an existing Prometheus install + type: boolean + default: true + group: "Prometheus" + - variable: prometheus.kubeStateMetrics.enabled + label: Enable KubeStateMetrics + description: "If true, deploy kube-state-metrics for Kubernetes metrics" + type: boolean + default: true + show_if: "global.prometheus.enabled=true" + group: "Prometheus" + - variable: prometheus.server.retention + label: Prometheus Server Retention + description: "Determines when to remove old data" + type: string + default: "15d" + show_if: "global.prometheus.enabled=true" + group: "Prometheus" + - variable: prometheus.server.persistentVolume.enabled + label: Create Persistent Volume for Prometheus + description: "If true, prometheus will create a persistent volume claim" + type: boolean + required: true + default: false + group: "Prometheus" + show_if: "global.prometheus.enabled=true" + show_subquestion_if: true + subquestions: + - variable: prometheus.server.persistentVolume.size + label: Prometheus Persistent Volume Size + type: string + default: "8Gi" + - variable: prometheus.server.persistentVolume.storageClass + label: Prometheus Persistent Volume StorageClass + description: "Prometheus data persistent volume storageClass, if not set use default StorageClass" + default: "" + type: storageclass + - variable: prometheus.server.persistentVolume.existingClaim + label: Existing Persistent Volume Claim for Prometheus + description: "If not empty, uses the specified existing PVC instead of creating new one" + type: pvc + default: "" + + # Prometheus Node Exporter + - variable: prometheus.nodeExporter.enabled + label: Enable NodeExporter + description: "If false, do not create NodeExporter daemonset" + type: boolean + default: true + group: "NodeExporter" + - variable: prometheus.serviceAccounts.nodeExporter.create + label: Enable Service Accounts NodeExporter + description: "If false, do not create NodeExporter daemonset" + type: boolean + default: true + group: "NodeExporter" + + # Prometheus AlertManager + - variable: prometheus.alertmanager.enabled + label: Enable AlertManager + type: boolean + default: false + group: "AlertManager" + - variable: prometheus.alertmanager.persistentVolume.enabled + label: Create Persistent Volume for AlertManager + description: "If true, alertmanager will create a persistent volume claim" + type: boolean + required: true + default: false + group: "AlertManager" + show_if: "prometheus.alertmanager.enabled=true" + show_subquestion_if: true + subquestions: + - variable: prometheus.alertmanager.persistentVolume.size + default: "2Gi" + description: "AlertManager data persistent volume size" + type: string + label: AlertManager Persistent Volume Size + - variable: prometheus.alertmanager.persistentVolume.storageClass + default: "" + description: "Alertmanager data persistent volume storageClass, if not set use default StorageClass" + type: storageclass + label: AlertManager Persistent Volume StorageClass + - variable: prometheus.alertmanager.persistentVolume.existingClaim + default: "" + description: "If not empty, uses the specified existing PVC instead of creating new one" + type: pvc + label: Existing Persistent Volume Claim for AlertManager + + # PushGateway + - variable: prometheus.pushgateway.enabled + label: Enable PushGateway + type: boolean + default: false + group: "PushGateway" + - variable: prometheus.pushgateway.persistentVolume.enabled + label: Create Persistent Volume for PushGateway + description: "If true, PushGateway will create a persistent volume claim" + required: true + type: boolean + default: false + group: "PushGateway" + show_if: "prometheus.pushgateway.enabled=true" + show_subquestion_if: true + subquestions: + - variable: prometheus.prometheus.pushgateway.persistentVolume.size + label: PushGateway Persistent Volume Size + type: string + default: "2Gi" + - variable: prometheus.pushgateway.persistentVolume.storageClass + label: PushGateway Persistent Volume StorageClass + description: "PushGateway data persistent volume storageClass, if not set use default StorageClass" + type: storageclass + default: "" + - variable: prometheus.pushgateway.persistentVolume.existingClaim + label: Existing Persistent Volume Claim for PushGateway + description: "If not empty, uses the specified existing PVC instead of creating new one" + type: pvc + default: "" + + # Services and Load Balancing + - variable: ingress.enabled + label: Enable Ingress + description: "Expose app using Ingress (Layer 7 Load Balancer)" + default: false + type: boolean + show_subquestion_if: true + group: "Services and Load Balancing" + subquestions: + - variable: ingress.hosts[0] + default: "xip.io" + description: "Hostname to your CostAnalyzer installation" + type: hostname + required: true + label: Hostname diff --git a/charts/kubecost/cost-analyzer/2.3.4/scripts/create-admission-controller-tls.sh b/charts/kubecost/cost-analyzer/2.3.4/scripts/create-admission-controller-tls.sh new file mode 100644 index 000000000..2290cadd1 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/scripts/create-admission-controller-tls.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -eo pipefail + +if [ -z "$1" ]; then + namespace=kubecost +else + namespace="$1" +fi + +echo -e "\nCreating certificates ..." +mkdir certs +openssl genrsa -out certs/tls.key 2048 +openssl req -new -key certs/tls.key -out certs/tls.csr -subj "/CN=webhook-server.${namespace}.svc" +openssl x509 -req -days 500 -extfile <(printf "subjectAltName=DNS:webhook-server.%s.svc" "${namespace}") -in certs/tls.csr -signkey certs/tls.key -out certs/tls.crt + +echo -e "\nCreating Webhook Server TLS Secret ..." +kubectl create secret tls webhook-server-tls \ + --cert "certs/tls.crt" \ + --key "certs/tls.key" -n "${namespace}" + +ENCODED_CA=$(base64 < certs/tls.crt | tr -d '\n') + +if [ -f "../values.yaml" ]; then + echo -e "\nUpdating values.yaml ..." + sed -i '' 's@${CA_BUNDLE}@'"${ENCODED_CA}"'@g' ../values.yaml +else + echo -e "\nThe CA bundle to use in your values file is: \n${ENCODED_CA}" +fi \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/NOTES.txt b/charts/kubecost/cost-analyzer/2.3.4/templates/NOTES.txt new file mode 100644 index 000000000..21bd8b1cd --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/NOTES.txt @@ -0,0 +1,27 @@ +-------------------------------------------------- +{{- include "kubecostV2-preconditions" . -}} +{{- include "cloudIntegrationSourceCheck" . -}} +{{- include "eksCheck" . -}} +{{- include "cloudIntegrationSecretCheck" . -}} +{{- include "gcpCloudIntegrationCheck" . -}} +{{- include "azureCloudIntegrationCheck" . -}} +{{- include "federatedStorageConfigSecretCheck" . -}} +{{- include "prometheusRetentionCheck" . -}} +{{- include "clusterIDCheck" . -}} + +{{- $servicePort := .Values.service.port | default 9090 }} +Kubecost {{ .Chart.Version }} has been successfully installed. + +Kubecost 2.x is a major upgrade from previous versions and includes major new features including a brand new API Backend. Please review the following documentation to ensure a smooth transition: https://docs.kubecost.com/install-and-configure/install/kubecostv2 + +When pods are Ready, you can enable port-forwarding with the following command: + + kubectl port-forward --namespace {{ .Release.Namespace }} deployment/{{ template "cost-analyzer.fullname" . }} {{ $servicePort }} + +Then, navigate to http://localhost:{{ $servicePort }} in a web browser. + +Please allow 25 minutes for Kubecost to gather metrics. A progress indicator will appear at the top of the UI. + +Having installation issues? View our Troubleshooting Guide at http://docs.kubecost.com/troubleshoot-install + +{{- include "kubecostV2-3-notices" . -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/_helpers.tpl b/charts/kubecost/cost-analyzer/2.3.4/templates/_helpers.tpl new file mode 100644 index 000000000..e80161bbd --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/_helpers.tpl @@ -0,0 +1,1463 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Set important variables before starting main templates +*/}} +{{- define "aggregator.deployMethod" -}} + {{- if (.Values.federatedETL).primaryCluster }} + {{- printf "statefulset" }} + {{- else if or ((.Values.federatedETL).agentOnly) (.Values.agent) (.Values.cloudAgent) }} + {{- printf "disabled" }} + {{- else if (not .Values.kubecostAggregator) }} + {{- printf "singlepod" }} + {{- else if .Values.kubecostAggregator.enabled }} + {{- printf "statefulset" }} + {{- else if eq .Values.kubecostAggregator.deployMethod "singlepod" }} + {{- printf "singlepod" }} + {{- else if eq .Values.kubecostAggregator.deployMethod "statefulset" }} + {{- printf "statefulset" }} + {{- else if eq .Values.kubecostAggregator.deployMethod "disabled" }} + {{- printf "disabled" }} + {{- else }} + {{- fail "Unknown kubecostAggregator.deployMethod value" }} + {{- end }} +{{- end }} + +{{- define "frontend.deployMethod" -}} + {{- if eq .Values.kubecostFrontend.deployMethod "haMode" -}} + {{- printf "haMode" -}} + {{- else -}} + {{- printf "singlepod" -}} + {{- end -}} +{{- end -}} + +{{/* +Kubecost 2.3 notices +*/}} +{{- define "kubecostV2-3-notices" -}} + {{- if (.Values.kubecostAggregator).env -}} + {{- printf "\n\n\nNotice: Issue in values detected.\nKubecost 2.3 has updated the aggregator's environment variables. Please update your Helm values to use the new key pairs.\nFor more information, see: https://docs.kubecost.com/install-and-configure/install/multi-cluster/federated-etl/aggregator#aggregator-optimizations\nIn Kubecost 2.3, kubecostAggregator.env is no longer used in favor of the new key pairs. This was done to prevent unexpected behavior and to simplify the aggregator's configuration." -}} + {{- end -}} +{{- end -}} + +{{/* +Kubecost 2.0 preconditions +*/}} +{{- define "kubecostV2-preconditions" -}} + {{/* Iterate through all StatefulSets in the namespace and check if any of them have a label indicating they are from + a pre-2.0 Helm Chart (e.g. "helm.sh/chart: cost-analyzer-1.108.1"). If so, return an error message with details and + documentation for how to properly upgrade to Kubecost 2.0 */}} + {{- $sts := (lookup "apps/v1" "StatefulSet" .Release.Namespace "") -}} + {{- if not (empty $sts.items) -}} + {{- range $index, $sts := $sts.items -}} + {{- if contains "aggregator" $sts.metadata.name -}} + {{- if $sts.metadata.labels -}} + {{- $stsLabels := $sts.metadata.labels -}} {{/* helm.sh/chart: cost-analyzer-1.108.1 */}} + {{- if hasKey $stsLabels "helm.sh/chart" -}} + {{- $chartLabel := index $stsLabels "helm.sh/chart" -}} {{/* cost-analyzer-1.108.1 */}} + {{- $chartNameAndVersion := split "-" $chartLabel -}} {{/* _0:cost _1:analyzer _2:1.108.1 */}} + {{- if gt (len $chartNameAndVersion) 2 -}} + {{- $chartVersion := $chartNameAndVersion._2 -}} {{/* 1.108.1 */}} + {{- if semverCompare ">=1.0.0-0 <2.0.0-0" $chartVersion -}} + {{- fail "\n\nAn existing Aggregator StatefulSet was found in your namespace.\nBefore upgrading to Kubecost 2.x, please `kubectl delete` this Statefulset.\nRefer to the following documentation for more information: https://docs.kubecost.com/install-and-configure/install/kubecostv2" -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{/*https://github.com/helm/helm/issues/8026#issuecomment-881216078*/}} + {{- if ((.Values.thanos).store).enabled -}} + {{- fail "\n\nYou are attempting to upgrade to Kubecost 2.x.\nKubecost no longer includes Thanos by default. \nPlease see https://docs.kubecost.com/install-and-configure/install/kubecostv2 for more information.\nIf you have any questions or concerns, please reach out to us at product@kubecost.com" -}} + {{- end -}} + + {{- if or ((.Values.saml).rbac).enabled ((.Values.oidc).rbac).enabled -}} + {{- if (not (.Values.upgrade).toV2) -}} + {{- fail "\n\nSSO with RBAC is enabled.\nNote that Kubecost 2.x has significant architectural changes that may impact RBAC.\nThis should be tested before giving end-users access to the UI.\nKubecost has tested various configurations and believe that 2.x will be 100% compatible with existing configurations.\nRefer to the following documentation for more information: https://docs.kubecost.com/install-and-configure/install/kubecostv2\n\nWhen ready to upgrade, add `--set upgrade.toV2=true`." -}} + {{- end -}} + {{- end -}} + + {{- if not .Values.kubecostModel.etlFileStoreEnabled -}} + {{- fail "\n\nKubecost 2.0 does not support running fully in-memory. Some file system must be available to store cost data." -}} + {{- end -}} + + + {{- if .Values.kubecostModel.openSourceOnly -}} + {{- fail "In Kubecost 2.0, kubecostModel.openSourceOnly is not supported" -}} + {{- end -}} + + {{/* Aggregator config reconciliation and common config */}} + {{- if eq (include "aggregator.deployMethod" .) "statefulset" -}} + {{- if .Values.kubecostAggregator -}} + {{- if (not .Values.kubecostAggregator.aggregatorDbStorage) -}} + {{- fail "In Enterprise configuration, Aggregator DB storage is required" -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- if (.Values.podSecurityPolicy).enabled }} + {{- fail "Kubecost no longer includes PodSecurityPolicy by default. Please take steps to preserve your existing PSPs before attempting the installation/upgrade again with the podSecurityPolicy values removed." }} + {{- end }} + + {{- if ((.Values.kubecostDeployment).leaderFollower).enabled -}} + {{- fail "\nIn Kubecost 2.0, kubecostDeployment does not support running as leaderFollower. Please reach out to support to discuss upgrade paths." -}} + {{- end -}} + + {{- if ((.Values.kubecostDeployment).statefulSet).enabled -}} + {{- fail "\nIn Kubecost 2.0, kubecostDeployment does not support running as a statefulSet. Please reach out to support to discuss upgrade paths." -}} + {{- end -}} + {{- if and (eq (include "aggregator.deployMethod" .) "statefulset") (.Values.federatedETL).agentOnly }} + {{- fail "\nKubecost does not support running federatedETL.agentOnly with the aggregator statefulset" }} + {{- end }} +{{- end -}} + +{{- define "cloudIntegrationFromProductConfigs" }} + { + {{- if ((.Values.kubecostProductConfigs).athenaBucketName) }} + "aws": [ + { + "athenaBucketName": "{{ .Values.kubecostProductConfigs.athenaBucketName }}", + "athenaRegion": "{{ .Values.kubecostProductConfigs.athenaRegion }}", + "athenaDatabase": "{{ .Values.kubecostProductConfigs.athenaDatabase }}", + "athenaTable": "{{ .Values.kubecostProductConfigs.athenaTable }}", + "projectID": "{{ .Values.kubecostProductConfigs.athenaProjectID }}" + {{ if (.Values.kubecostProductConfigs).athenaWorkgroup }} + , "athenaWorkgroup": "{{ .Values.kubecostProductConfigs.athenaWorkgroup }}" + {{ else }} + , "athenaWorkgroup": "primary" + {{ end }} + {{ if (.Values.kubecostProductConfigs).masterPayerARN }} + , "masterPayerARN": "{{ .Values.kubecostProductConfigs.masterPayerARN }}" + {{ end }} + {{- if and ((.Values.kubecostProductConfigs).awsServiceKeyName) ((.Values.kubecostProductConfigs).awsServiceKeyPassword) }}, + "serviceKeyName": "{{ .Values.kubecostProductConfigs.awsServiceKeyName }}", + "serviceKeySecret": "{{ .Values.kubecostProductConfigs.awsServiceKeyPassword }}" + {{- end }} + } + ] + {{- end }} + } +{{- end }} + +{{/* +Cloud integration source contents check. Either the Secret must be specified or the JSON, not both. +Additionally, for upgrade protection, certain individual values populated under the kubecostProductConfigs map, if found, +will result in failure. Users are asked to select one of the two presently-available sources for cloud integration information. +*/}} +{{- define "cloudIntegrationSourceCheck" -}} + {{- if and (.Values.kubecostProductConfigs).cloudIntegrationSecret (.Values.kubecostProductConfigs).cloudIntegrationJSON -}} + {{- fail "\nkubecostProductConfigs.cloudIntegrationSecret and kubecostProductConfigs.cloudIntegrationJSON are mutually exclusive. Please specify only one." -}} + {{- end -}} + {{- if and (.Values.kubecostProductConfigs).cloudIntegrationSecret ((.Values.kubecostProductConfigs).athenaBucketName) }} + {{- fail "\nkubecostProductConfigs.cloudIntegrationSecret and kubecostProductConfigs.athena* values are mutually exclusive. Please specifiy only one." -}} + {{- end -}} +{{- if and (.Values.kubecostProductConfigs).cloudIntegrationJSON ((.Values.kubecostProductConfigs).athenaBucketName) }} + {{- fail "\nkubecostProductConfigs.cloudIntegrationJSON and kubecostProductConfigs.athena* values are mutually exclusive. Please specifiy only one." -}} + {{- end -}} +{{- end -}} + + +{{/* +Print a warning if PV is enabled AND EKS is detected AND the EBS-CSI driver is not installed +*/}} +{{- define "eksCheck" }} +{{- $isEKS := (regexMatch ".*eks.*" (.Capabilities.KubeVersion | quote) )}} +{{- $isGT22 := (semverCompare ">=1.23-0" .Capabilities.KubeVersion.GitVersion) }} +{{- $PVNotExists := (empty (lookup "v1" "PersistentVolume" "" "")) }} +{{- $EBSCSINotExists := (empty (lookup "apps/v1" "Deployment" "kube-system" "ebs-csi-controller")) }} +{{- if (and $isEKS $isGT22 .Values.persistentVolume.enabled $EBSCSINotExists) -}} + +ERROR: MISSING EBS-CSI DRIVER WHICH IS REQUIRED ON EKS v1.23+ TO MANAGE PERSISTENT VOLUMES. LEARN MORE HERE: https://docs.kubecost.com/install-and-configure/install/provider-installations/aws-eks-cost-monitoring#prerequisites + +{{- end -}} +{{- end -}} + +{{/* +Verify a cluster_id is set in the Prometheus global config +*/}} +{{- define "clusterIDCheck" -}} + {{- if (.Values.kubecostModel).federatedStorageConfigSecret }} + {{- if not .Values.prometheus.server.clusterIDConfigmap }} + {{- if eq .Values.prometheus.server.global.external_labels.cluster_id "cluster-one" }} + {{- fail "\n\nWhen using multi-cluster Kubecost, you must specify a unique `.Values.prometheus.server.global.external_labels.cluster_id` for each cluster.\nNote this must be set even if you are using your own Prometheus or another identifier.\n" -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + + +{{/* +Verify the cloud integration secret exists with the expected key when cloud integration is enabled. +Skip the check if CI/CD is enabled and skipSanityChecks is set. Argo CD, for example, does not +support templating a chart which uses the lookup function. +*/}} +{{- define "cloudIntegrationSecretCheck" -}} +{{- if (.Values.kubecostProductConfigs).cloudIntegrationSecret }} +{{- if not (and .Values.global.platforms.cicd.enabled .Values.global.platforms.cicd.skipSanityChecks) }} +{{- if .Capabilities.APIVersions.Has "v1/Secret" }} + {{- $secret := lookup "v1" "Secret" .Release.Namespace .Values.kubecostProductConfigs.cloudIntegrationSecret }} + {{- if or (not $secret) (not (index $secret.data "cloud-integration.json")) }} + {{- fail (printf "The cloud integration secret '%s' does not exist or does not contain the expected key 'cloud-integration.json'\nIf you are using `--dry-run`, please add `--dry-run=server`. This requires Helm 3.13+." .Values.kubecostProductConfigs.cloudIntegrationSecret) }} + {{- end }} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Verify the federated storage config secret exists with the expected key when cloud integration is enabled. +Skip the check if CI/CD is enabled and skipSanityChecks is set. Argo CD, for example, does not +support templating a chart which uses the lookup function. +*/}} +{{- define "federatedStorageConfigSecretCheck" -}} +{{- if (.Values.kubecostModel).federatedStorageConfigSecret }} +{{- if not (and .Values.global.platforms.cicd.enabled .Values.global.platforms.cicd.skipSanityChecks) }} +{{- if .Capabilities.APIVersions.Has "v1/Secret" }} + {{- $secret := lookup "v1" "Secret" .Release.Namespace .Values.kubecostModel.federatedStorageConfigSecret }} + {{- if or (not $secret) (not (index $secret.data "federated-store.yaml")) }} + {{- fail (printf "The federated storage config secret '%s' does not exist or does not contain the expected key 'federated-store.yaml'" .Values.kubecostModel.federatedStorageConfigSecret) }} + {{- end }} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* + Ensure that the Prometheus retention is not set too low +*/}} +{{- define "prometheusRetentionCheck" }} +{{- if ((.Values.prometheus).server).enabled }} + + {{- $retention := .Values.prometheus.server.retention }} + {{- $etlHourlyDurationHours := (int .Values.kubecostModel.etlHourlyStoreDurationHours) }} + + {{- if (hasSuffix "d" $retention) }} + {{- $retentionDays := (int (trimSuffix "d" $retention)) }} + {{- if lt $retentionDays 3 }} + {{- fail (printf "With a daily resolution, Prometheus retention must be set >= 3 days. Provided retention is %s" $retention) }} + {{- else if le (mul $retentionDays 24) $etlHourlyDurationHours }} + {{- fail (printf "Prometheus retention (%s) must be greater than .Values.kubecostModel.etlHourlyStoreDurationHours (%d)" $retention $etlHourlyDurationHours) }} + {{- end }} + + {{- else if (hasSuffix "h" $retention) }} + {{- $retentionHours := (int (trimSuffix "h" $retention)) }} + {{- if lt $retentionHours 50 }} + {{- fail (printf "With an hourly resolution, Prometheus retention must be set >= 50 hours. Provided retention is %s" $retention) }} + {{- else if le $retentionHours $etlHourlyDurationHours }} + {{- fail (printf "Prometheus retention (%s) must be greater than .Values.kubecostModel.etlHourlyStoreDurationHours (%d)" $retention $etlHourlyDurationHours) }} + {{- end }} + + {{- else }} + {{- fail "prometheus.server.retention must be set in days (e.g. 5d) or hours (e.g. 97h)"}} + + {{- end }} +{{- end }} +{{- end }} + +{{/* +Expand the name of the chart. +*/}} +{{- define "cost-analyzer.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "aggregator.name" -}} +{{- default "aggregator" | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "cloudCost.name" -}} +{{- default "cloud-cost" | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "etlUtils.name" -}} +{{- default "etl-utils" | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "forecasting.name" -}} +{{- default "forecasting" | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "frontend.name" -}} +{{- default "frontend" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "cost-analyzer.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{- define "diagnostics.fullname" -}} +{{- if .Values.diagnosticsFullnameOverride -}} +{{- .Values.diagnosticsFullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name "diagnostics" | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{- define "aggregator.fullname" -}} +{{- printf "%s-%s" .Release.Name "aggregator" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "cloudCost.fullname" -}} +{{- printf "%s-%s" .Release.Name (include "cloudCost.name" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "etlUtils.fullname" -}} +{{- printf "%s-%s" .Release.Name (include "etlUtils.name" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "forecasting.fullname" -}} +{{- printf "%s-%s" .Release.Name (include "forecasting.name" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "frontend.fullname" -}} +{{- printf "%s-%s" .Release.Name (include "frontend.name" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create the fully qualified name for Prometheus server service. +*/}} +{{- define "cost-analyzer.prometheus.server.name" -}} +{{- if .Values.prometheus -}} +{{- if .Values.prometheus.server -}} +{{- if .Values.prometheus.server.fullnameOverride -}} +{{- .Values.prometheus.server.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-prometheus-server" .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- else -}} +{{- printf "%s-prometheus-server" .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- else -}} +{{- printf "%s-prometheus-server" .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* +Create the fully qualified name for Prometheus alertmanager service. +*/}} +{{- define "cost-analyzer.prometheus.alertmanager.name" -}} +{{- if .Values.prometheus -}} +{{- if .Values.prometheus.alertmanager -}} +{{- if .Values.prometheus.alertmanager.fullnameOverride -}} +{{- .Values.prometheus.alertmanager.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-prometheus-alertmanager" .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- else -}} +{{- printf "%s-prometheus-alertmanager" .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- else -}} +{{- printf "%s-prometheus-alertmanager" .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{- define "cost-analyzer.serviceName" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{- define "frontend.serviceName" -}} +{{ include "frontend.fullname" . }} +{{- end -}} + +{{- define "diagnostics.serviceName" -}} +{{- printf "%s-%s" .Release.Name "diagnostics" | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "aggregator.serviceName" -}} +{{- printf "%s-%s" .Release.Name "aggregator" | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "cloudCost.serviceName" -}} +{{ include "cloudCost.fullname" . }} +{{- end -}} +{{- define "etlUtils.serviceName" -}} +{{ include "etlUtils.fullname" . }} +{{- end -}} +{{- define "forecasting.serviceName" -}} +{{ include "forecasting.fullname" . }} +{{- end -}} + +{{/* +Create the name of the service account +*/}} +{{- define "cost-analyzer.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "cost-analyzer.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} +{{- define "aggregator.serviceAccountName" -}} +{{- if .Values.kubecostAggregator.serviceAccountName -}} + {{ .Values.kubecostAggregator.serviceAccountName }} +{{- else -}} + {{ template "cost-analyzer.serviceAccountName" . }} +{{- end -}} +{{- end -}} +{{- define "cloudCost.serviceAccountName" -}} +{{- if .Values.kubecostAggregator.cloudCost.serviceAccountName -}} + {{ .Values.kubecostAggregator.cloudCost.serviceAccountName }} +{{- else -}} + {{ template "cost-analyzer.serviceAccountName" . }} +{{- end -}} +{{- end -}} +{{/* +Network Costs name used to tie autodiscovery of metrics to daemon set pods +*/}} +{{- define "cost-analyzer.networkCostsName" -}} +{{- printf "%s-%s" .Release.Name "network-costs" -}} +{{- end -}} + +{{- define "kubecost.clusterControllerName" -}} +{{- printf "%s-%s" .Release.Name "cluster-controller" -}} +{{- end -}} + +{{- define "kubecost.kubeMetricsName" -}} +{{- if .Values.agent }} +{{- printf "%s-%s" .Release.Name "agent" -}} +{{- else if .Values.cloudAgent }} +{{- printf "%s-%s" .Release.Name "cloud-agent" -}} +{{- else }} +{{- printf "%s-%s" .Release.Name "metrics" -}} +{{- end }} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "cost-analyzer.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create the chart labels. +*/}} +{{- define "cost-analyzer.chartLabels" -}} +helm.sh/chart: {{ include "cost-analyzer.chart" . }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} +{{- define "kubecost.chartLabels" -}} +app.kubernetes.io/name: {{ include "cost-analyzer.name" . }} +helm.sh/chart: {{ include "cost-analyzer.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} +{{- define "kubecost.aggregator.chartLabels" -}} +app.kubernetes.io/name: {{ include "aggregator.name" . }} +helm.sh/chart: {{ include "cost-analyzer.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + + +{{/* +Create the common labels. +*/}} +{{- define "cost-analyzer.commonLabels" -}} +app.kubernetes.io/name: {{ include "cost-analyzer.name" . }} +helm.sh/chart: {{ include "cost-analyzer.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app: cost-analyzer +{{- end -}} + +{{- define "aggregator.commonLabels" -}} +{{ include "cost-analyzer.chartLabels" . }} +app: aggregator +{{- end -}} + +{{- define "diagnostics.commonLabels" -}} +{{ include "cost-analyzer.chartLabels" . }} +app: diagnostics +{{- end -}} + +{{- define "cloudCost.commonLabels" -}} +{{ include "cost-analyzer.chartLabels" . }} +{{ include "cloudCost.selectorLabels" . }} +{{- end -}} + +{{- define "etlUtils.commonLabels" -}} +{{ include "cost-analyzer.chartLabels" . }} +{{ include "etlUtils.selectorLabels" . }} +{{- end -}} +{{- define "forecasting.commonLabels" -}} +{{ include "cost-analyzer.chartLabels" . }} +{{ include "forecasting.selectorLabels" . }} +{{- end -}} + +{{/* +Create the networkcosts common labels. Note that because this is a daemonset, we don't want app.kubernetes.io/instance: to take the release name, which allows the scrape config to be static. +*/}} +{{- define "networkcosts.commonLabels" -}} +app.kubernetes.io/instance: kubecost +app.kubernetes.io/name: network-costs +helm.sh/chart: {{ include "cost-analyzer.chart" . }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app: {{ template "cost-analyzer.networkCostsName" . }} +{{- end -}} +{{- define "networkcosts.selectorLabels" -}} +app: {{ template "cost-analyzer.networkCostsName" . }} +{{- end }} +{{- define "diagnostics.selectorLabels" -}} +app.kubernetes.io/name: diagnostics +app.kubernetes.io/instance: {{ .Release.Name }} +app: diagnostics +{{- end }} + +{{/* +Create the selector labels. +*/}} +{{- define "cost-analyzer.selectorLabels" -}} +app.kubernetes.io/name: {{ include "cost-analyzer.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app: cost-analyzer +{{- end -}} + +{{/* +Create the selector labels for haMode frontend. +*/}} +{{- define "frontend.selectorLabels" -}} +app.kubernetes.io/name: {{ include "frontend.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app: cost-analyzer +{{- end -}} + +{{- define "aggregator.selectorLabels" -}} +{{- if eq (include "aggregator.deployMethod" .) "statefulset" }} +app.kubernetes.io/name: {{ include "aggregator.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app: aggregator +{{- else if eq (include "aggregator.deployMethod" .) "singlepod" }} +{{- include "cost-analyzer.selectorLabels" . }} +{{- else }} +{{ fail "Failed to set aggregator.selectorLabels" }} +{{- end }} +{{- end }} + +{{- define "cloudCost.selectorLabels" -}} +{{- if eq (include "aggregator.deployMethod" .) "statefulset" }} +app.kubernetes.io/name: {{ include "cloudCost.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app: {{ include "cloudCost.name" . }} +{{- else }} +{{- include "cost-analyzer.selectorLabels" . }} +{{- end }} +{{- end }} + +{{- define "forecasting.selectorLabels" -}} +app.kubernetes.io/name: {{ include "forecasting.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app: {{ include "forecasting.name" . }} +{{- end -}} +{{- define "etlUtils.selectorLabels" -}} +app.kubernetes.io/name: {{ include "etlUtils.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app: {{ include "etlUtils.name" . }} +{{- end -}} + +{{/* +Recursive filter which accepts a map containing an input map (.v) and an output map (.r). The template +will traverse all values inside .v recursively writing non-map values to the output .r. If a nested map +is discovered, we look for an 'enabled' key. If it doesn't exist, we continue traversing the +map. If it does exist, we omit the inner map traversal iff enabled is false. This filter writes the +enabled only version to the output .r +*/}} +{{- define "cost-analyzer.filter" -}} +{{- $v := .v }} +{{- $r := .r }} +{{- range $key, $value := .v }} + {{- $tp := kindOf $value -}} + {{- if eq $tp "map" -}} + {{- $isEnabled := true -}} + {{- if (hasKey $value "enabled") -}} + {{- $isEnabled = $value.enabled -}} + {{- end -}} + {{- if $isEnabled -}} + {{- $rr := "{}" | fromYaml }} + {{- template "cost-analyzer.filter" (dict "v" $value "r" $rr) }} + {{- $_ := set $r $key $rr -}} + {{- end -}} + {{- else -}} + {{- $_ := set $r $key $value -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +This template accepts a map and returns a base64 encoded json version of the map where all disabled +leaf nodes are omitted. + +The implied use case is {{ template "cost-analyzer.filterEnabled" .Values }} +*/}} +{{- define "cost-analyzer.filterEnabled" -}} +{{- $result := "{}" | fromYaml }} +{{- template "cost-analyzer.filter" (dict "v" . "r" $result) }} +{{- $result | toJson | b64enc }} +{{- end -}} + +{{/* +============================================================== +Begin Prometheus templates +============================================================== +*/}} +{{/* +Expand the name of the chart. +*/}} +{{- define "prometheus.name" -}} +{{- "prometheus" -}} +{{- end -}} + +{{/* +Define common selector labels for all Prometheus components +*/}} +{{- define "prometheus.common.matchLabels" -}} +app: {{ template "prometheus.name" . }} +release: {{ .Release.Name }} +{{- end -}} + +{{/* +Define common top-level labels for all Prometheus components +*/}} +{{- define "prometheus.common.metaLabels" -}} +heritage: {{ .Release.Service }} +{{- end -}} + +{{/* +Define top-level labels for Alert Manager +*/}} +{{- define "prometheus.alertmanager.labels" -}} +{{ include "prometheus.alertmanager.matchLabels" . }} +{{ include "prometheus.common.metaLabels" . }} +{{- end -}} + +{{/* +Define selector labels for Alert Manager +*/}} +{{- define "prometheus.alertmanager.matchLabels" -}} +component: {{ .Values.prometheus.alertmanager.name | quote }} +{{ include "prometheus.common.matchLabels" . }} +{{- end -}} + +{{/* +Define top-level labels for Node Exporter +*/}} +{{- define "prometheus.nodeExporter.labels" -}} +{{ include "prometheus.nodeExporter.matchLabels" . }} +{{ include "prometheus.common.metaLabels" . }} +{{- end -}} + +{{/* +Define selector labels for Node Exporter +*/}} +{{- define "prometheus.nodeExporter.matchLabels" -}} +component: {{ .Values.prometheus.nodeExporter.name | quote }} +{{ include "prometheus.common.matchLabels" . }} +{{- end -}} + +{{/* +Define top-level labels for Push Gateway +*/}} +{{- define "prometheus.pushgateway.labels" -}} +{{ include "prometheus.pushgateway.matchLabels" . }} +{{ include "prometheus.common.metaLabels" . }} +{{- end -}} + +{{/* +Define selector labels for Push Gateway +*/}} +{{- define "prometheus.pushgateway.matchLabels" -}} +component: {{ .Values.prometheus.pushgateway.name | quote }} +{{ include "prometheus.common.matchLabels" . }} +{{- end -}} + +{{/* +Define top-level labels for Server +*/}} +{{- define "prometheus.server.labels" -}} +{{ include "prometheus.server.matchLabels" . }} +{{ include "prometheus.common.metaLabels" . }} +{{- end -}} + +{{/* +Define selector labels for Server +*/}} +{{- define "prometheus.server.matchLabels" -}} +component: {{ .Values.prometheus.server.name | quote }} +{{ include "prometheus.common.matchLabels" . }} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "prometheus.fullname" -}} +{{- if .Values.prometheus.fullnameOverride -}} +{{- .Values.prometheus.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default "prometheus" .Values.prometheus.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create a fully qualified alertmanager name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} + +{{- define "prometheus.alertmanager.fullname" -}} +{{- if .Values.prometheus.alertmanager.fullnameOverride -}} +{{- .Values.prometheus.alertmanager.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default "prometheus" .Values.prometheus.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- printf "%s-%s" .Release.Name .Values.prometheus.alertmanager.name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s-%s" .Release.Name $name .Values.prometheus.alertmanager.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + + +{{/* +Create a fully qualified node-exporter name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "prometheus.nodeExporter.fullname" -}} +{{- if .Values.prometheus.nodeExporter.fullnameOverride -}} +{{- .Values.prometheus.nodeExporter.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default "prometheus" .Values.prometheus.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- printf "%s-%s" .Release.Name .Values.prometheus.nodeExporter.name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s-%s" .Release.Name $name .Values.prometheus.nodeExporter.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create a fully qualified Prometheus server name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "prometheus.server.fullname" -}} +{{- if .Values.prometheus.server.fullnameOverride -}} +{{- .Values.prometheus.server.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default "prometheus" .Values.prometheus.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- printf "%s-%s" .Release.Name .Values.prometheus.server.name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s-%s" .Release.Name $name .Values.prometheus.server.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create a fully qualified pushgateway name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "prometheus.pushgateway.fullname" -}} +{{- if .Values.prometheus.pushgateway.fullnameOverride -}} +{{- .Values.prometheus.pushgateway.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default "prometheus" .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- printf "%s-%s" .Release.Name .Values.prometheus.pushgateway.name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s-%s" .Release.Name $name .Values.prometheus.pushgateway.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the service account to use for the alertmanager component +*/}} +{{- define "prometheus.serviceAccountName.alertmanager" -}} +{{- if .Values.prometheus.serviceAccounts.alertmanager.create -}} + {{ default (include "prometheus.alertmanager.fullname" .) .Values.prometheus.serviceAccounts.alertmanager.name }} +{{- else -}} + {{ default "default" .Values.prometheus.serviceAccounts.alertmanager.name }} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the service account to use for the nodeExporter component +*/}} +{{- define "prometheus.serviceAccountName.nodeExporter" -}} +{{- if .Values.prometheus.serviceAccounts.nodeExporter.create -}} + {{ default (include "prometheus.nodeExporter.fullname" .) .Values.prometheus.serviceAccounts.nodeExporter.name }} +{{- else -}} + {{ default "default" .Values.prometheus.serviceAccounts.nodeExporter.name }} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the service account to use for the pushgateway component +*/}} +{{- define "prometheus.serviceAccountName.pushgateway" -}} +{{- if .Values.prometheus.serviceAccounts.pushgateway.create -}} + {{ default (include "prometheus.pushgateway.fullname" .) .Values.prometheus.serviceAccounts.pushgateway.name }} +{{- else -}} + {{ default "default" .Values.prometheus.serviceAccounts.pushgateway.name }} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the service account to use for the server component +*/}} +{{- define "prometheus.serviceAccountName.server" -}} +{{- if .Values.prometheus.serviceAccounts.server.create -}} + {{ default (include "prometheus.server.fullname" .) .Values.prometheus.serviceAccounts.server.name }} +{{- else -}} + {{ default "default" .Values.prometheus.serviceAccounts.server.name }} +{{- end -}} +{{- end -}} + +{{/* +============================================================== +Begin Grafana templates +============================================================== +*/}} +{{/* +Expand the name of the chart. +*/}} +{{- define "grafana.name" -}} +{{- "grafana" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "grafana.fullname" -}} +{{- if .Values.grafana.fullnameOverride -}} +{{- .Values.grafana.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default "grafana" .Values.grafana.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the service account +*/}} +{{- define "grafana.serviceAccountName" -}} +{{- if .Values.grafana.serviceAccount.create -}} + {{ default (include "grafana.fullname" .) .Values.grafana.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.grafana.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +============================================================== +Begin Kubecost 2.0 templates +============================================================== +*/}} + +{{- define "aggregator.containerTemplate" }} +- name: aggregator +{{- if .Values.kubecostAggregator.containerSecurityContext }} + securityContext: + {{- toYaml .Values.kubecostAggregator.containerSecurityContext | nindent 4 }} +{{- else if .Values.global.containerSecurityContext }} + securityContext: + {{- toYaml .Values.global.containerSecurityContext | nindent 4 }} +{{- end }} + {{- if .Values.kubecostModel }} + {{- if .Values.kubecostAggregator.fullImageName }} + image: {{ .Values.kubecostAggregator.fullImageName }} + {{- else if .Values.imageVersion }} + image: {{ .Values.kubecostModel.image }}:{{ .Values.imageVersion }} + {{- else if eq "development" .Chart.AppVersion }} + image: gcr.io/kubecost1/cost-model-nightly:latest + {{- else }} + image: {{ .Values.kubecostModel.image }}:prod-{{ $.Chart.AppVersion }} + {{- end }} + {{- else }} + image: gcr.io/kubecost1/cost-model:prod-{{ $.Chart.AppVersion }} + {{- end }} + {{- if .Values.kubecostAggregator.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: /healthz + port: 9004 + initialDelaySeconds: {{ .Values.kubecostAggregator.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.kubecostAggregator.readinessProbe.periodSeconds }} + failureThreshold: {{ .Values.kubecostAggregator.readinessProbe.failureThreshold }} + {{- end }} + {{- if .Values.kubecostAggregator.imagePullPolicy }} + imagePullPolicy: {{ .Values.kubecostAggregator.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + args: ["waterfowl"] + ports: + - name: tcp-api + containerPort: 9004 + protocol: TCP + {{- with.Values.kubecostAggregator.extraPorts }} + {{- toYaml . | nindent 4 }} + {{- end }} + resources: + {{- toYaml .Values.kubecostAggregator.resources | nindent 4 }} + volumeMounts: + - name: persistent-configs + mountPath: /var/configs + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: federated-storage-config + mountPath: /var/configs/etl + readOnly: true + {{- else if eq (include "aggregator.deployMethod" .) "statefulset" }} + {{- fail "When in StatefulSet mode, Aggregator requires that kubecostModel.federatedStorageConfigSecret be set." }} + {{- end }} + {{- if and .Values.persistentVolume.dbPVEnabled (eq (include "aggregator.deployMethod" .) "singlepod") }} + - name: persistent-db + mountPath: /var/db + # aggregator should only need read access to ETL data + readOnly: true + {{- end }} + {{- if eq (include "aggregator.deployMethod" .) "statefulset" }} + - name: aggregator-db-storage + mountPath: /var/configs/waterfowl/duckdb + - name: aggregator-staging + # Aggregator uses /var/configs/waterfowl as a "staging" directory for + # things like intermediate-state files pre-ingestion. In order to avoid a + # permission problem similar to + # https://github.com/kubernetes/kubernetes/issues/81676, we create an + # emptyDir at this path. + # + # This hasn't been observed as a problem in cost-analyzer, likely because + # of the init container that gives everything under /var/configs 777. + mountPath: /var/configs/waterfowl + {{- end }} + {{- if and ((.Values.kubecostProductConfigs).productKey).enabled ((.Values.kubecostProductConfigs).productKey).secretname (eq (include "aggregator.deployMethod" .) "statefulset") }} + - name: productkey-secret + mountPath: /var/configs/productkey + {{- end }} + {{- if and ((.Values.kubecostProductConfigs).smtp).secretname (eq (include "aggregator.deployMethod" .) "statefulset") }} + - name: smtp-secret + mountPath: /var/configs/smtp + {{- end }} + {{- if .Values.saml }} + {{- if .Values.saml.enabled }} + {{- if .Values.saml.secretName }} + - name: secret-volume + mountPath: /var/configs/secret-volume + {{- end }} + {{- if .Values.saml.encryptionCertSecret }} + - name: saml-encryption-cert + mountPath: /var/configs/saml-encryption-cert + {{- end }} + {{- if .Values.saml.decryptionKeySecret }} + - name: saml-decryption-key + mountPath: /var/configs/saml-decryption-key + {{- end }} + {{- if .Values.saml.metadataSecretName }} + - name: metadata-secret-volume + mountPath: /var/configs/metadata-secret-volume + {{- end }} + - name: saml-auth-secret + mountPath: /var/configs/saml-auth-secret + {{- if .Values.saml.rbac.enabled }} + - name: saml-roles + mountPath: /var/configs/saml + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.oidc }} + {{- if .Values.oidc.enabled }} + - name: oidc-config + mountPath: /var/configs/oidc + {{- if or .Values.oidc.existingCustomSecret.name .Values.oidc.secretName }} + - name: oidc-client-secret + mountPath: /var/configs/oidc-client-secret + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.global.integrations.postgres.enabled }} + - name: postgres-creds + mountPath: /var/configs/integrations/postgres-creds + - name: postgres-queries + mountPath: /var/configs/integrations/postgres-queries + {{- end }} + {{- /* Only adds extraVolumeMounts if aggregator is running as its own pod */}} + {{- if and .Values.kubecostAggregator.extraVolumeMounts (eq (include "aggregator.deployMethod" .) "statefulset") }} + {{- toYaml .Values.kubecostAggregator.extraVolumeMounts | nindent 4 }} + {{- end }} + env: + {{- if and (.Values.prometheus.server.global.external_labels.cluster_id) (not .Values.prometheus.server.clusterIDConfigmap) }} + - name: CLUSTER_ID + value: {{ .Values.prometheus.server.global.external_labels.cluster_id }} + {{- end }} + {{- if .Values.prometheus.server.clusterIDConfigmap }} + - name: CLUSTER_ID + valueFrom: + configMapKeyRef: + name: {{ .Values.prometheus.server.clusterIDConfigmap }} + key: CLUSTER_ID + {{- end }} + {{- if and ((.Values.kubecostProductConfigs).productKey).mountPath (eq (include "aggregator.deployMethod" .) "statefulset") }} + - name: PRODUCT_KEY_MOUNT_PATH + value: {{ .Values.kubecostProductConfigs.productKey.mountPath }} + {{- end }} + {{- if and ((.Values.kubecostProductConfigs).smtp).mountPath (eq (include "aggregator.deployMethod" .) "statefulset") }} + - name: SMTP_CONFIG_MOUNT_PATH + value: {{ .Values.kubecostProductConfigs.smtp.mountPath }} + {{- end }} + {{- if .Values.smtpConfigmapName }} + - name: SMTP_CONFIGMAP_NAME + value: {{ .Values.smtpConfigmapName }} + {{- end }} + {{- if (gt (int .Values.kubecostAggregator.numDBCopyPartitions) 0) }} + - name: NUM_DB_COPY_CHUNKS + value: {{ .Values.kubecostAggregator.numDBCopyPartitions | quote }} + {{- end }} + {{- if .Values.kubecostAggregator.jaeger.enabled }} + - name: TRACING_URL + value: "http://localhost:14268/api/traces" + {{- end }} + - name: CONFIG_PATH + value: /var/configs/ + {{- if and .Values.persistentVolume.dbPVEnabled (eq (include "aggregator.deployMethod" .) "singlepod") }} + - name: ETL_PATH_PREFIX + value: "/var/db" + {{- end }} + - name: CLOUD_PROVIDER_API_KEY + value: "AIzaSyDXQPG_MHUEy9neR7stolq6l0ujXmjJlvk" # The GCP Pricing API key.This GCP api key is expected to be here and is limited to accessing google's billing API.' + {{- if .Values.global.integrations.postgres.enabled }} + - name: AGGREGATOR_ADDRESS + {{- if or .Values.saml.enabled .Values.oidc.enabled }} + value: localhost:9008 + {{- else }} + value: localhost:9004 + {{- end }} + - name: INT_PG_ENABLED + value: "true" + - name: INT_PG_RUN_INTERVAL + value: {{ quote .Values.global.integrations.postgres.runInterval }} + {{- end }} + - name: READ_ONLY + value: {{ (quote .Values.readonly) | default (quote false) }} + {{- if .Values.systemProxy.enabled }} + - name: HTTP_PROXY + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: http_proxy + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: HTTPS_PROXY + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: https_proxy + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: NO_PROXY + value: {{ .Values.systemProxy.noProxy }} + - name: no_proxy + value: {{ .Values.systemProxy.noProxy }} + {{- end }} + {{- if ((.Values.kubecostProductConfigs).carbonEstimates) }} + - name: CARBON_ESTIMATES_ENABLED + value: "true" + {{- end }} + - name: CUSTOM_COST_ENABLED + value: {{ .Values.kubecostModel.plugins.enabled | quote }} + {{- if .Values.kubecostAggregator.extraEnv -}} + {{- toYaml .Values.kubecostAggregator.extraEnv | nindent 4 }} + {{- end }} + {{- if eq (include "aggregator.deployMethod" .) "statefulset" }} + # If this isn't set, we pretty much have to be in a read only state, + # initialization will probably fail otherwise. + - name: ETL_BUCKET_CONFIG + {{- if not .Values.kubecostModel.federatedStorageConfigSecret }} + value: /var/configs/etl/object-store.yaml + {{- else }} + value: /var/configs/etl/federated-store.yaml + - name: FEDERATED_STORE_CONFIG + value: /var/configs/etl/federated-store.yaml + - name: FEDERATED_PRIMARY_CLUSTER # this ensures the ingester runs assuming federated primary paths in the bucket + value: "true" + - name: FEDERATED_CLUSTER # this ensures the ingester runs assuming federated primary paths in the bucket + value: "true" + {{- end }} + {{- end }} + - name: LOG_LEVEL + value: {{ .Values.kubecostAggregator.logLevel }} + - name: DB_COPY_FULL + value: {{ (quote .Values.kubecostAggregator.dbCopyFull) | default (quote true) }} + - name: DB_READ_THREADS + value: {{ .Values.kubecostAggregator.dbReadThreads | quote }} + - name: DB_WRITE_THREADS + value: {{ .Values.kubecostAggregator.dbWriteThreads | quote }} + - name: DB_CONCURRENT_INGESTION_COUNT + value: {{ .Values.kubecostAggregator.dbConcurrentIngestionCount | quote }} + {{- if ne .Values.kubecostAggregator.dbMemoryLimit "0GB" }} + - name: DB_MEMORY_LIMIT + value: {{ .Values.kubecostAggregator.dbMemoryLimit | quote }} + {{- end }} + {{- if ne .Values.kubecostAggregator.dbWriteMemoryLimit "0GB" }} + - name: DB_WRITE_MEMORY_LIMIT + value: {{ .Values.kubecostAggregator.dbWriteMemoryLimit | quote }} + {{- end }} + - name: ETL_DAILY_STORE_DURATION_DAYS + value: {{ .Values.kubecostAggregator.etlDailyStoreDurationDays | quote }} + - name: DB_TRIM_MEMORY_ON_CLOSE + value: {{ .Values.kubecostAggregator.dbTrimMemoryOnClose | quote }} + - name: KUBECOST_NAMESPACE + value: {{ .Release.Namespace }} + {{- if .Values.oidc.enabled }} + - name: OIDC_ENABLED + value: "true" + - name: OIDC_SKIP_ONLINE_VALIDATION + value: {{ (quote .Values.oidc.skipOnlineTokenValidation) | default (quote false) }} + {{- end}} + {{- if .Values.kubecostAggregator }} + {{- if .Values.kubecostAggregator.collections }} + {{- if (((.Values.kubecostAggregator).collections).cache) }} + - name: COLLECTIONS_MEMORY_CACHE_ENABLED + value: {{ (quote .Values.kubecostAggregator.collections.cache.enabled) | default (quote true) }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.saml }} + {{- if .Values.saml.enabled }} + - name: SAML_ENABLED + value: "true" + - name: IDP_URL + value: {{ .Values.saml.idpMetadataURL }} + - name: SP_HOST + value: {{ .Values.saml.appRootURL }} + {{- if .Values.saml.audienceURI }} + - name: AUDIENCE_URI + value: {{ .Values.saml.audienceURI }} + {{- end }} + {{- if .Values.saml.isGLUUProvider }} + - name: GLUU_SAML_PROVIDER + value: {{ (quote .Values.saml.isGLUUProvider) }} + {{- end }} + {{- if .Values.saml.nameIDFormat }} + - name: NAME_ID_FORMAT + value: {{ .Values.saml.nameIDFormat }} + {{- end}} + {{- if .Values.saml.authTimeout }} + - name: AUTH_TOKEN_TIMEOUT + value: {{ (quote .Values.saml.authTimeout) }} + {{- end}} + {{- if .Values.saml.redirectURL }} + - name: LOGOUT_REDIRECT_URL + value: {{ .Values.saml.redirectURL }} + {{- end}} + {{- if .Values.saml.rbac.enabled }} + - name: SAML_RBAC_ENABLED + value: "true" + {{- end }} + {{- if and .Values.saml.encryptionCertSecret .Values.saml.decryptionKeySecret }} + - name: SAML_RESPONSE_ENCRYPTED + value: "true" + {{- end}} + {{- end }} + {{- end }} +{{- end }} + + +{{- define "aggregator.jaeger.sidecarContainerTemplate" }} +- name: embedded-jaeger + env: + - name: SPAN_STORAGE_TYPE + value: badger + - name: BADGER_EPHEMERAL + value: "true" + - name: BADGER_DIRECTORY_VALUE + value: /tmp/badger/data + - name: BADGER_DIRECTORY_KEY + value: /tmp/badger/key + securityContext: + {{- toYaml .Values.kubecostAggregator.jaeger.containerSecurityContext | nindent 4 }} + image: {{ .Values.kubecostAggregator.jaeger.image }}:{{ .Values.kubecostAggregator.jaeger.imageVersion }} +{{- end }} + + +{{- define "aggregator.cloudCost.containerTemplate" }} +- name: cloud-cost + {{- if .Values.kubecostModel }} + {{- if .Values.kubecostAggregator.fullImageName }} + image: {{ .Values.kubecostAggregator.fullImageName }} + {{- else if .Values.kubecostModel.fullImageName }} + image: {{ .Values.kubecostModel.fullImageName }} + {{- else if .Values.imageVersion }} + image: {{ .Values.kubecostModel.image }}:{{ .Values.imageVersion }} + {{- else if eq "development" .Chart.AppVersion }} + image: gcr.io/kubecost1/cost-model-nightly:latest + {{- else }} + image: {{ .Values.kubecostModel.image }}:prod-{{ $.Chart.AppVersion }} + {{ end }} + {{- else }} + image: gcr.io/kubecost1/cost-model:prod-{{ $.Chart.AppVersion }} + {{ end }} + {{- if .Values.kubecostAggregator.cloudCost.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: /healthz + port: 9005 + initialDelaySeconds: {{ .Values.kubecostAggregator.cloudCost.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.kubecostAggregator.cloudCost.readinessProbe.periodSeconds }} + failureThreshold: {{ .Values.kubecostAggregator.cloudCost.readinessProbe.failureThreshold }} + {{- end }} + {{- if .Values.kubecostAggregator.imagePullPolicy }} + imagePullPolicy: {{ .Values.kubecostAggregator.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + args: ["cloud-cost"] + ports: + - name: tcp-api + containerPort: 9005 + protocol: TCP + resources: + {{- toYaml .Values.kubecostAggregator.cloudCost.resources | nindent 4 }} + securityContext: + {{- if .Values.global.containerSecurityContext }} + {{- toYaml .Values.global.containerSecurityContext | nindent 4 }} + {{- end }} + volumeMounts: + - name: persistent-configs + mountPath: /var/configs + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: federated-storage-config + mountPath: /var/configs/etl/federated + readOnly: true + {{- else if .Values.kubecostModel.etlBucketConfigSecret }} + - name: etl-bucket-config + mountPath: /var/configs/etl + readOnly: true + {{- end }} + {{- if or (.Values.kubecostProductConfigs).cloudIntegrationSecret (.Values.kubecostProductConfigs).cloudIntegrationJSON ((.Values.kubecostProductConfigs).athenaBucketName) }} + - name: cloud-integration + mountPath: /var/configs/cloud-integration + {{- end }} + {{- if .Values.kubecostModel.plugins.enabled }} + - mountPath: {{ .Values.kubecostModel.plugins.folder }} + name: plugins-dir + readOnly: false + - name: tmp + mountPath: /tmp + - mountPath: {{ $.Values.kubecostModel.plugins.folder }}/config + name: plugins-config + readOnly: true + {{- end }} + {{- /* Only adds extraVolumeMounts when cloudcosts is running as its own pod */}} + {{- if and .Values.kubecostAggregator.cloudCost.extraVolumeMounts (eq (include "aggregator.deployMethod" .) "statefulset") }} + {{- toYaml .Values.kubecostAggregator.cloudCost.extraVolumeMounts | nindent 4 }} + {{- end }} + env: + - name: CONFIG_PATH + value: /var/configs/ + {{- if .Values.kubecostModel.etlBucketConfigSecret }} + - name: ETL_BUCKET_CONFIG + value: /var/configs/etl/object-store.yaml + {{- end}} + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: FEDERATED_STORE_CONFIG + value: /var/configs/etl/federated/federated-store.yaml + - name: FEDERATED_CLUSTER + value: "true" + {{- end}} + - name: ETL_DAILY_STORE_DURATION_DAYS + value: {{ (quote .Values.kubecostModel.etlDailyStoreDurationDays) }} + - name: CLOUD_COST_REFRESH_RATE_HOURS + value: {{ .Values.kubecostAggregator.cloudCost.refreshRateHours | default 6 | quote }} + - name: CLOUD_COST_QUERY_WINDOW_DAYS + value: {{ .Values.kubecostAggregator.cloudCost.queryWindowDays | default 7 | quote }} + - name: CLOUD_COST_RUN_WINDOW_DAYS + value: {{ .Values.kubecostAggregator.cloudCost.runWindowDays | default 3 | quote }} + - name: CUSTOM_COST_ENABLED + value: {{ .Values.kubecostModel.plugins.enabled | quote }} + {{- range $key, $value := .Values.kubecostAggregator.cloudCost.env }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + {{- if .Values.systemProxy.enabled }} + - name: HTTP_PROXY + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: http_proxy + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: HTTPS_PROXY + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: https_proxy + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: NO_PROXY + value: {{ .Values.systemProxy.noProxy }} + - name: no_proxy + value: {{ .Values.systemProxy.noProxy }} + {{- end }} +{{- end }} + +{{/* +SSO enabled flag for nginx configmap +*/}} +{{- define "ssoEnabled" -}} + {{- if or (.Values.saml).enabled (.Values.oidc).enabled -}} + {{- printf "true" -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} +{{- end -}} + +{{/* +To use the Kubecost built-in Teams UI RBAC< you must enable SSO and RBAC and not specify any groups. +Groups is only used when using external RBAC. +*/}} +{{- define "rbacTeamsEnabled" -}} + {{- if or (.Values.saml).enabled (.Values.oidc).enabled -}} + {{- if or ((.Values.saml).rbac).enabled ((.Values.oidc).rbac).enabled -}} + {{- if not (or (.Values.saml).groups (.Values.oidc).groups) -}} + {{- printf "true" -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} +{{- end -}} + +{{/* +Backups configured flag for nginx configmap +*/}} +{{- define "dataBackupConfigured" -}} + {{- if or (.Values.kubecostModel).etlBucketConfigSecret (.Values.kubecostModel).federatedStorageConfigSecret -}} + {{- printf "true" -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} +{{- end -}} + +{{/* +costEventsAuditEnabled flag for nginx configmap +*/}} +{{- define "costEventsAuditEnabled" -}} + {{- if or (.Values.costEventsAudit).enabled -}} + {{- printf "true" -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} +{{- end -}} + +{{- define "cost-analyzer.grafanaEnabled" -}} + {{- if and (.Values.global.grafana.enabled) (not .Values.federatedETL.agentOnly) -}} + {{- printf "true" -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} +{{- end -}} + +{{- define "gcpCloudIntegrationJSON" }} +Kubecost 2.x requires a change to the method that cloud-provider billing integrations are configured. +Please use this output to create a cloud-integration.json config. See: + +for more information + + { + "gcp": + { + [ + { + "bigQueryBillingDataDataset": "{{ .Values.kubecostProductConfigs.bigQueryBillingDataDataset }}", + "bigQueryBillingDataProject": "{{ .Values.kubecostProductConfigs.bigQueryBillingDataProject }}", + "bigQueryBillingDataTable": "{{ .Values.kubecostProductConfigs.bigQueryBillingDataTable }}", + "projectID": "{{ .Values.kubecostProductConfigs.projectID }}" + } + ] + } + } +{{- end }} + +{{- define "gcpCloudIntegrationCheck" }} +{{- if ((.Values.kubecostProductConfigs).bigQueryBillingDataDataset) }} +{{- fail (include "gcpCloudIntegrationJSON" .) }} +{{- end }} +{{- end }} + + +{{- define "azureCloudIntegrationJSON" }} + +Kubecost 2.x requires a change to the method that cloud-provider billing integrations are configured. +Please use this output to create a cloud-integration.json config. See: + +for more information + { + "azure": + [ + { + "azureStorageContainer": "{{ .Values.kubecostProductConfigs.azureStorageContainer }}", + "azureSubscriptionID": "{{ .Values.kubecostProductConfigs.azureSubscriptionID }}", + "azureStorageAccount": "{{ .Values.kubecostProductConfigs.azureStorageAccount }}", + "azureStorageAccessKey": "{{ .Values.kubecostProductConfigs.azureStorageKey }}", + "azureContainerPath": "{{ .Values.kubecostProductConfigs.azureContainerPath }}", + "azureCloud": "{{ .Values.kubecostProductConfigs.azureCloud }}" + } + ] + } +{{- end }} + +{{- define "azureCloudIntegrationCheck" }} +{{- if ((.Values.kubecostProductConfigs).azureStorageContainer) }} +{{- fail (include "azureCloudIntegrationJSON" .) }} +{{- end }} +{{- end }} + +{{- define "clusterControllerEnabled" }} +{{- if (.Values.clusterController).enabled }} +{{- printf "true" -}} +{{- else -}} +{{- printf "false" -}} +{{- end -}} +{{- end -}} + +{{- define "forecastingEnabled" }} +{{- if (.Values.forecasting).enabled }} +{{- printf "true" -}} +{{- else -}} +{{- printf "false" -}} +{{- end -}} +{{- end -}} + +{{- define "pluginsEnabled" }} +{{- if (.Values.kubecostModel.plugins).enabled }} +{{- printf "true" -}} +{{- else -}} +{{- printf "false" -}} +{{- end -}} +{{- end -}} + +{{- define "carbonEstimatesEnabled" }} +{{- if ((.Values.kubecostProductConfigs).carbonEstimates) }} +{{- printf "true" -}} +{{- else -}} +{{- printf "false" -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-cloud-cost-deployment.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-cloud-cost-deployment.yaml new file mode 100644 index 000000000..b1baf2ff2 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-cloud-cost-deployment.yaml @@ -0,0 +1,167 @@ +{{- if eq (include "aggregator.deployMethod" .) "statefulset" }} + +{{/* + A cloud integration secret is required for cloud cost to function as a dedicated pod. + UI based configuration is not supported for cloud cost with aggregator. +*/}} +{{- if ((.Values.kubecostAggregator).cloudCost).enabled }} +{{- if not ( or (.Values.kubecostProductConfigs).cloudIntegrationSecret (.Values.kubecostProductConfigs).cloudIntegrationJSON ((.Values.kubecostProductConfigs).athenaBucketName)) }} +{{- fail "\n\nA cloud-integration secret is required when using the aggregator statefulset and cloudCost is enabled." }} +{{- end }} +{{- end }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "cloudCost.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cloudCost.commonLabels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: 1 + selector: + matchLabels: + {{ include "cloudCost.selectorLabels" . | nindent 6 }} + strategy: + type: Recreate + template: + metadata: + labels: + {{/* + Force pod restarts on upgrades to ensure the nginx config is current + */}} + {{- if not .Values.global.platforms.cicd.enabled }} + helm-rollout-restarter: {{ randAlphaNum 5 | quote }} + {{- end }} + app.kubernetes.io/name: cloud-cost + app.kubernetes.io/instance: {{ .Release.Name }} + app: cloud-cost + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.global.podAnnotations}} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- if .Values.global.platforms.openshift.enabled }} + securityContext: + {{- toYaml .Values.global.platforms.openshift.securityContext | nindent 8 }} + {{- else if .Values.global.securityContext }} + securityContext: + {{- toYaml .Values.global.securityContext | nindent 8 }} + {{- else }} + securityContext: + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + {{- end }} + restartPolicy: Always + serviceAccountName: {{ template "cloudCost.serviceAccountName" . }} + volumes: + {{- if .Values.kubecostModel.etlBucketConfigSecret }} + - name: etl-bucket-config + secret: + defaultMode: 420 + secretName: {{ .Values.kubecostModel.etlBucketConfigSecret }} + {{- end }} + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: federated-storage-config + secret: + defaultMode: 420 + secretName: {{ .Values.kubecostModel.federatedStorageConfigSecret }} + {{- end }} + {{- if (.Values.kubecostProductConfigs).cloudIntegrationSecret }} + - name: cloud-integration + secret: + secretName: {{ .Values.kubecostProductConfigs.cloudIntegrationSecret }} + items: + - key: cloud-integration.json + path: cloud-integration.json + {{- else if or (.Values.kubecostProductConfigs).cloudIntegrationJSON ((.Values.kubecostProductConfigs).athenaProjectID) }} + - name: cloud-integration + secret: + secretName: cloud-integration + items: + - key: cloud-integration.json + path: cloud-integration.json + {{- end }} + {{/* Despite the name, this is not persistent-configs. + The name is for compatibility with single-pod install. + All data stored here is ephemeral, and does not require persistence. */}} + - name: persistent-configs + emptyDir: {} + {{- if .Values.kubecostModel.plugins.enabled }} + {{- if .Values.kubecostModel.plugins.install.enabled}} + - name: install-script + configMap: + name: {{ template "cost-analyzer.fullname" . }}-install-plugins + {{- end }} + - name: plugins-dir + emptyDir: {} + {{- if and (not .Values.kubecostModel.plugins.existingCustomSecret.enabled) .Values.kubecostModel.plugins.secretName }} + - name: plugins-config + secret: + secretName: {{ .Values.kubecostModel.plugins.secretName }} + items: + - key: datadog_config.json + path: datadog_config.json + {{- end }} + {{- if .Values.kubecostModel.plugins.existingCustomSecret.enabled }} + - name: plugins-config + secret: + secretName: {{ .Values.kubecostModel.plugins.existingCustomSecret.name }} + items: + - key: datadog_config.json + path: datadog_config.json + {{- end }} + - name: tmp + emptyDir: {} + {{- end }} + {{- if .Values.kubecostAggregator.cloudCost.extraVolumes }} + {{- toYaml .Values.kubecostAggregator.cloudCost.extraVolumes | nindent 8 }} + {{- end }} + initContainers: + {{- if (and .Values.kubecostModel.plugins.enabled .Values.kubecostModel.plugins.install.enabled )}} + - name: plugin-installer + image: {{ .Values.kubecostModel.plugins.install.fullImageName }} + command: ["sh", "/install/install_plugins.sh"] + {{- with .Values.kubecostModel.plugins.install.securityContext }} + securityContext: {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: install-script + mountPath: /install + - name: plugins-dir + mountPath: {{ .Values.kubecostModel.plugins.folder }} + {{- end }} + containers: + {{- include "aggregator.cloudCost.containerTemplate" . | nindent 8 }} + {{- if .Values.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.kubecostAggregator.priority }} + {{- if .Values.kubecostAggregator.priority.enabled }} + {{- if .Values.kubecostAggregator.priority.name }} + priorityClassName: {{ .Values.kubecostAggregator.priority.name }} + {{- else }} + priorityClassName: {{ template "cost-analyzer.fullname" . }}-aggregator-priority + {{- end }} + {{- end }} + {{- end }} + {{- with .Values.kubecostAggregator.cloudCost.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.kubecostAggregator.cloudCost.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.kubecostAggregator.cloudCost.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-cloud-cost-service-account.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-cloud-cost-service-account.yaml new file mode 100644 index 000000000..c8018f77b --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-cloud-cost-service-account.yaml @@ -0,0 +1,23 @@ +{{- if eq (include "aggregator.deployMethod" .) "statefulset" }} + +{{/* + A cloud integration secret is required for cloud cost to function as a dedicated pod. + UI based configuration is not supported for cloud cost with aggregator. +*/}} + +{{- if or (.Values.kubecostProductConfigs).cloudIntegrationSecret (.Values.kubecostProductConfigs).cloudIntegrationJSON ((.Values.kubecostProductConfigs).athenaBucketName) }} +{{- if and .Values.serviceAccount.create .Values.kubecostAggregator.cloudCost.serviceAccountName }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "cloudCost.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cloudCost.commonLabels" . | nindent 4 }} +{{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-cloud-cost-service.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-cloud-cost-service.yaml new file mode 100644 index 000000000..bef9bfdc5 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-cloud-cost-service.yaml @@ -0,0 +1,17 @@ +{{- if not (eq (include "aggregator.deployMethod" .) "disabled") -}} +kind: Service +apiVersion: v1 +metadata: + name: {{ template "cloudCost.serviceName" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "cloudCost.commonLabels" . | nindent 4 }} +spec: + selector: +{{ include "cloudCost.selectorLabels" . | nindent 4 }} + type: "ClusterIP" + ports: + - name: tcp-api + port: 9005 + targetPort: 9005 +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-service.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-service.yaml new file mode 100644 index 000000000..40f6729de --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-service.yaml @@ -0,0 +1,25 @@ +{{- if not (eq (include "aggregator.deployMethod" .) "disabled") -}} +kind: Service +apiVersion: v1 +metadata: + name: {{ template "aggregator.serviceName" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "aggregator.commonLabels" . | nindent 4 }} +spec: + selector: +{{ include "aggregator.selectorLabels" . | nindent 4 }} + type: "ClusterIP" + ports: + - name: tcp-api + port: 9004 + targetPort: 9004 + {{- if or .Values.saml.enabled .Values.oidc.enabled}} + - name: apiserver + port: 9008 + targetPort: 9008 + {{- end }} + {{- with .Values.kubecostAggregator.extraPorts }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-servicemonitor.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-servicemonitor.yaml new file mode 100644 index 000000000..670ae4794 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-servicemonitor.yaml @@ -0,0 +1,31 @@ +{{- if .Values.serviceMonitor.aggregatorMetrics.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "aggregator.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "aggregator.commonLabels" . | nindent 4 }} + {{- if .Values.serviceMonitor.aggregatorMetrics.additionalLabels }} + {{ toYaml .Values.serviceMonitor.aggregatorMetrics.additionalLabels | nindent 4 }} + {{- end }} +spec: + endpoints: + - port: tcp-api + interval: {{ .Values.serviceMonitor.aggregatorMetrics.interval }} + scrapeTimeout: {{ .Values.serviceMonitor.aggregatorMetrics.scrapeTimeout }} + path: /metrics + scheme: http + {{- with .Values.serviceMonitor.aggregatorMetrics.metricRelabelings }} + metricRelabelings: {{ toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.serviceMonitor.aggregatorMetrics.relabelings }} + relabelings: {{ toYaml . | nindent 8 }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + {{- include "aggregator.commonLabels" . | nindent 6 }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-statefulset.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-statefulset.yaml new file mode 100644 index 000000000..bea16d077 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/aggregator-statefulset.yaml @@ -0,0 +1,199 @@ +{{- if and (not .Values.agent) (not .Values.cloudAgent) }} +{{- if eq (include "aggregator.deployMethod" .) "statefulset" }} + +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ template "aggregator.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "aggregator.commonLabels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.kubecostAggregator.replicas }} + serviceName: {{ template "aggregator.serviceName" . }} + selector: + matchLabels: + {{- include "aggregator.selectorLabels" . | nindent 6 }} + volumeClaimTemplates: + - metadata: + name: aggregator-db-storage + spec: + accessModes: [ "ReadWriteOnce" ] + storageClassName: {{ .Values.kubecostAggregator.aggregatorDbStorage.storageClass }} + resources: + requests: + storage: {{ .Values.kubecostAggregator.aggregatorDbStorage.storageRequest }} + - metadata: + # In the StatefulSet config, Aggregator should not share any filesystem + # state with the cost-model to maintain independence and improve + # stability (in the event of bad file-locking state). Still, there is + # a need to "mount" ConfigMap files (using the watcher) to a file system; + # that's what this per-replica Volume is used for. + name: persistent-configs + spec: + accessModes: [ "ReadWriteOnce" ] + storageClassName: {{ .Values.kubecostAggregator.persistentConfigsStorage.storageClass }} + resources: + requests: + storage: {{ .Values.kubecostAggregator.persistentConfigsStorage.storageRequest }} + template: + metadata: + labels: + app.kubernetes.io/name: aggregator + app.kubernetes.io/instance: {{ .Release.Name }} + {{/* Force pod restarts on upgrades to ensure the nginx config is current */}} + {{- if not .Values.global.platforms.cicd.enabled }} + helm-rollout-restarter: {{ randAlphaNum 5 | quote }} + {{- end }} + app: aggregator + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.global.podAnnotations}} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + restartPolicy: Always + {{- if .Values.kubecostAggregator.securityContext }} + securityContext: + {{- toYaml .Values.kubecostAggregator.securityContext | nindent 8 }} + {{- else if and (.Values.global.platforms.openshift.enabled) (.Values.global.platforms.openshift.securityContext) }} + securityContext: + {{- toYaml .Values.global.platforms.openshift.securityContext | nindent 8 }} + {{- else if .Values.global.securityContext }} + securityContext: + {{- toYaml .Values.global.securityContext | nindent 8 }} + {{- end }} + serviceAccountName: {{ template "aggregator.serviceAccountName" . }} + volumes: + - name: aggregator-staging + emptyDir: + sizeLimit: {{ .Values.kubecostAggregator.stagingEmptyDirSizeLimit }} + {{- $etlBackupBucketSecret := "" }} + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + {{- $etlBackupBucketSecret = .Values.kubecostModel.federatedStorageConfigSecret }} + {{- end }} + {{- if $etlBackupBucketSecret }} + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: federated-storage-config + secret: + defaultMode: 420 + secretName: {{ .Values.kubecostModel.federatedStorageConfigSecret }} + {{- end }} + - name: etl-bucket-config + secret: + defaultMode: 420 + secretName: {{ $etlBackupBucketSecret }} + {{- else }} + {{- fail "\n\nKubecost Aggregator Enterprise Config requires .Values.kubecostModel.federatedStorageConfigSecret" }} + {{- end }} + {{- if and ((.Values.kubecostProductConfigs).productKey).enabled ((.Values.kubecostProductConfigs).productKey).secretname }} + - name: productkey-secret + secret: + secretName: {{ .Values.kubecostProductConfigs.productKey.secretname }} + items: + - key: productkey.json + path: productkey.json + {{- end }} + {{- if .Values.saml }} + {{- if .Values.saml.enabled }} + {{- if .Values.saml.secretName }} + - name: secret-volume + secret: + secretName: {{ .Values.saml.secretName }} + {{- end }} + {{- if .Values.saml.encryptionCertSecret }} + - name: saml-encryption-cert + secret: + secretName: {{ .Values.saml.encryptionCertSecret }} + {{- end }} + {{- if .Values.saml.decryptionKeySecret }} + - name: saml-decryption-key + secret: + secretName: {{ .Values.saml.decryptionKeySecret }} + {{- end }} + {{- if .Values.saml.metadataSecretName }} + - name: metadata-secret-volume + secret: + secretName: {{ .Values.saml.metadataSecretName }} + {{- end }} + - name: saml-auth-secret + secret: + secretName: {{ .Values.saml.authSecretName | default "kubecost-saml-secret" }} + {{- if .Values.saml.rbac.enabled }} + - name: saml-roles + configMap: + name: {{ template "cost-analyzer.fullname" . }}-saml + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.oidc }} + {{- if .Values.oidc.enabled }} + - name: oidc-config + configMap: + name: {{ template "cost-analyzer.fullname" . }}-oidc + {{- if and (not .Values.oidc.existingCustomSecret.enabled) .Values.oidc.secretName }} + - name: oidc-client-secret + secret: + secretName: {{ .Values.oidc.secretName }} + {{- end }} + {{- if .Values.oidc.existingCustomSecret.enabled }} + - name: oidc-client-secret + secret: + secretName: {{ .Values.oidc.existingCustomSecret.name }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.global.integrations.postgres.enabled }} + - name: postgres-creds + secret: + {{- if not (eq .Values.global.integrations.postgres.databaseSecretName "") }} + secretName: {{ .Values.global.integrations.postgres.databaseSecretName }} + {{- else }} + secretName: kubecost-integrations-postgres + {{- end }} + - name: postgres-queries + configMap: + name: kubecost-integrations-postgres-queries + {{- end }} + {{- if .Values.kubecostAggregator.extraVolumes }} + {{- toYaml .Values.kubecostAggregator.extraVolumes | nindent 8 }} + {{- end }} + containers: + {{- include "aggregator.containerTemplate" . | nindent 8 }} + + {{- if .Values.kubecostAggregator.jaeger.enabled }} + {{ include "aggregator.jaeger.sidecarContainerTemplate" . | nindent 8 }} + {{- end }} + + {{- if .Values.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.kubecostAggregator.priority }} + {{- if .Values.kubecostAggregator.priority.enabled }} + {{- if .Values.kubecostAggregator.priority.name }} + priorityClassName: {{ .Values.kubecostAggregator.priority.name }} + {{- else }} + priorityClassName: {{ template "cost-analyzer.fullname" . }}-aggregator-priority + {{- end }} + {{- end }} + {{- end }} + {{- with .Values.kubecostAggregator.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.kubecostAggregator.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.kubecostAggregator.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/alibaba-service-key-secret.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/alibaba-service-key-secret.yaml new file mode 100644 index 000000000..bffb7d8fe --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/alibaba-service-key-secret.yaml @@ -0,0 +1,20 @@ +{{- if .Values.kubecostProductConfigs }} +{{- if .Values.kubecostProductConfigs.createServiceKeySecret }} +{{- if .Values.kubecostProductConfigs.alibabaServiceKeyName }} +apiVersion: v1 +kind: Secret +metadata: + name: cloud-service-key + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +type: Opaque +stringData: + service-key.json: |- + { + "alibaba_access_key_id": "{{ .Values.kubecostProductConfigs.alibabaServiceKeyName }}", + "alibaba_secret_access_key": "{{ .Values.kubecostProductConfigs.alibabaServiceKeyPassword }}" + } +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/aws-service-key-secret.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/aws-service-key-secret.yaml new file mode 100644 index 000000000..eeecc03f9 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/aws-service-key-secret.yaml @@ -0,0 +1,20 @@ +{{- if .Values.kubecostProductConfigs }} +{{- if .Values.kubecostProductConfigs.createServiceKeySecret }} +{{- if .Values.kubecostProductConfigs.awsServiceKeyName }} +apiVersion: v1 +kind: Secret +metadata: + name: cloud-service-key + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +type: Opaque +stringData: + service-key.json: |- + { + "aws_access_key_id": "{{ .Values.kubecostProductConfigs.awsServiceKeyName }}", + "aws_secret_access_key": "{{ .Values.kubecostProductConfigs.awsServiceKeyPassword }}" + } +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/awsstore-deployment-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/awsstore-deployment-template.yaml new file mode 100644 index 000000000..6a1eb5f8e --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/awsstore-deployment-template.yaml @@ -0,0 +1,49 @@ +{{- if .Values.awsstore }} +{{- if .Values.awsstore.useAwsStore }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "cost-analyzer.fullname" . }}-awsstore + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + selector: + matchLabels: + app: awsstore + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app: awsstore + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.global.podAnnotations}} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: awsstore-serviceaccount + {{- if .Values.awsstore.priorityClassName }} + priorityClassName: "{{ .Values.awsstore.priorityClassName }}" + {{- end }} + {{- with .Values.awsstore.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - image: {{ .Values.awsstore.imageNameAndVersion }} + name: awsstore + # Just sleep forever + command: [ "sleep" ] + args: [ "infinity" ] +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/awsstore-service-account-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/awsstore-service-account-template.yaml new file mode 100644 index 000000000..0dadeaacc --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/awsstore-service-account-template.yaml @@ -0,0 +1,15 @@ +{{- if .Values.awsstore }} +{{- if .Values.awsstore.createServiceAccount }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: awsstore-serviceaccount + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +{{- with .Values.awsstore.annotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/azure-service-key-secret.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/azure-service-key-secret.yaml new file mode 100644 index 000000000..e61b61e86 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/azure-service-key-secret.yaml @@ -0,0 +1,24 @@ +{{- if .Values.kubecostProductConfigs }} +{{- if .Values.kubecostProductConfigs.createServiceKeySecret }} +{{- if .Values.kubecostProductConfigs.azureSubscriptionID }} +apiVersion: v1 +kind: Secret +metadata: + name: cloud-service-key + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +type: Opaque +stringData: + service-key.json: |- + { + "subscriptionId": "{{ .Values.kubecostProductConfigs.azureSubscriptionID }}", + "serviceKey": { + "appId": "{{ .Values.kubecostProductConfigs.azureClientID }}", + "password": "{{ .Values.kubecostProductConfigs.azureClientPassword }}", + "tenant": "{{ .Values.kubecostProductConfigs.azureTenantID }}" + } + } +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/azure-storage-config-secret.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/azure-storage-config-secret.yaml new file mode 100644 index 000000000..f27cb4e89 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/azure-storage-config-secret.yaml @@ -0,0 +1,26 @@ +{{/*This method of configuration is deprecated*/}} +{{- if .Values.kubecostProductConfigs }} +{{- if .Values.kubecostProductConfigs.azureStorageCreateSecret }} +{{- if .Values.kubecostProductConfigs.azureStorageAccessKey }} +{{- if .Values.kubecostProductConfigs.azureStorageAccount }} +{{- if .Values.kubecostProductConfigs.azureStorageContainer }} +apiVersion: v1 +kind: Secret +metadata: + name: azure-storage-config + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +type: Opaque +stringData: + azure-storage-config.json: |- + { + "azureStorageAccount": "{{ .Values.kubecostProductConfigs.azureStorageAccount }}", + "azureStorageAccessKey": "{{ .Values.kubecostProductConfigs.azureStorageAccessKey }}", + "azureStorageContainer": "{{ .Values.kubecostProductConfigs.azureStorageContainer }}" + } +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cloud-integration-secret.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cloud-integration-secret.yaml new file mode 100644 index 000000000..e6023e59b --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cloud-integration-secret.yaml @@ -0,0 +1,16 @@ +{{- if or ((.Values.kubecostProductConfigs).cloudIntegrationJSON) ((.Values.kubecostProductConfigs).athenaBucketName) }} +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: cloud-integration + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + {{- if (.Values.kubecostProductConfigs).cloudIntegrationJSON }} + cloud-integration.json: {{ .Values.kubecostProductConfigs.cloudIntegrationJSON | replace "\n" "" | b64enc }} + {{- else }} + cloud-integration.json: {{ include "cloudIntegrationFromProductConfigs" . |nindent 4| replace "\n" "" | b64enc }} + {{- end }} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-account-mapping-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-account-mapping-configmap.yaml new file mode 100644 index 000000000..3c4902395 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-account-mapping-configmap.yaml @@ -0,0 +1,12 @@ +{{- if .Values.kubecostProductConfigs }} +{{- if .Values.kubecostProductConfigs.cloudAccountMapping }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: "account-mapping" + namespace: {{ .Release.Namespace }} + labels: {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + account-map.json: '{{ toJson .Values.kubecostProductConfigs.cloudAccountMapping }}' +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-advanced-reports-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-advanced-reports-configmap.yaml new file mode 100644 index 000000000..8b31cb0db --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-advanced-reports-configmap.yaml @@ -0,0 +1,13 @@ +{{- if .Values.global.advancedReports }} +{{- if .Values.global.advancedReports.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{default "advanced-report-configs" .Values.advancedReportConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + advanced-reports.json: '{{ toJson .Values.global.advancedReports.reports }}' +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-alerts-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-alerts-configmap.yaml new file mode 100644 index 000000000..3a2554411 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-alerts-configmap.yaml @@ -0,0 +1,10 @@ +{{- if .Values.global.notifications.alertConfigs }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ default "alert-configs" .Values.alertConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + alerts.json: '{{ toJson .Values.global.notifications.alertConfigs }}' +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-asset-reports-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-asset-reports-configmap.yaml new file mode 100644 index 000000000..387b0afc8 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-asset-reports-configmap.yaml @@ -0,0 +1,14 @@ +{{- if .Values.global.assetReports }} +{{- if .Values.global.assetReports.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ default "asset-report-configs" .Values.assetReportConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + asset-reports.json: '{{ toJson .Values.global.assetReports.reports }}' +{{- end -}} +{{- end -}} + diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-cloud-cost-reports-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-cloud-cost-reports-configmap.yaml new file mode 100644 index 000000000..97e74156f --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-cloud-cost-reports-configmap.yaml @@ -0,0 +1,13 @@ +{{- if .Values.global.cloudCostReports }} +{{- if .Values.global.cloudCostReports.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{default "cloud-cost-report-configs" .Values.cloudCostReportConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + cloud-cost-reports.json: '{{ toJson .Values.global.cloudCostReports.reports }}' +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-cluster-role-binding-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-cluster-role-binding-template.yaml new file mode 100644 index 000000000..91867dd90 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-cluster-role-binding-template.yaml @@ -0,0 +1,36 @@ +{{- if .Values.reporting }} +{{- if .Values.reporting.logCollection }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ template "cost-analyzer.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "cost-analyzer.serviceAccountName" . }} +subjects: + - kind: ServiceAccount + name: {{ template "cost-analyzer.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +--- +{{- end }} +{{- end }} +{{- if (not .Values.kubecostModel.etlReadOnlyMode) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ template "cost-analyzer.serviceAccountName" . }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "cost-analyzer.serviceAccountName" . }} +subjects: + - kind: ServiceAccount + name: {{ template "cost-analyzer.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-cluster-role-template-readonly.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-cluster-role-template-readonly.yaml new file mode 100644 index 000000000..c84f105e9 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-cluster-role-template-readonly.yaml @@ -0,0 +1,26 @@ +{{- if (.Values.kubecostModel.etlReadOnlyMode) }} +{{- if and .Values.reporting .Values.reporting.logCollection -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: {{ .Release.Namespace }} + name: {{ template "cost-analyzer.serviceAccountName" . }} +rules: + - apiGroups: + - '' + resources: + - "pods/log" + verbs: + - get + - list + - watch + - apiGroups: + - '' + resources: + - configmaps + verbs: + - get + - list + - watch +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-cluster-role-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-cluster-role-template.yaml new file mode 100644 index 000000000..a76d2fe55 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-cluster-role-template.yaml @@ -0,0 +1,108 @@ +{{- if not .Values.kubecostModel.etlReadOnlyMode -}} +{{- if and .Values.reporting .Values.reporting.logCollection -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: {{ .Release.Namespace }} + name: {{ template "cost-analyzer.serviceAccountName" . }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +rules: +- apiGroups: + - '' + resources: + - "pods/log" + verbs: + - get + - list + - watch +- apiGroups: + - '' + resources: + - configmaps + verbs: + - get + - list + - watch + - update +--- +{{- end }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "cost-analyzer.serviceAccountName" . }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +rules: + - apiGroups: + - '' + resources: + - configmaps + - nodes + - pods + - events + - services + - resourcequotas + - replicationcontrollers + - limitranges + - persistentvolumeclaims + - persistentvolumes + - namespaces + - endpoints + verbs: + - get + - list + - watch + - apiGroups: + - apps + resources: + - statefulsets + - deployments + - daemonsets + - replicasets + verbs: + - get + - list + - watch + - apiGroups: + - batch + resources: + - cronjobs + - jobs + verbs: + - get + - list + - watch + - apiGroups: + - autoscaling + resources: + - horizontalpodautoscalers + verbs: + - get + - list + - watch + - apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - get + - list + - watch + - apiGroups: + - storage.k8s.io + resources: + - storageclasses + verbs: + - get + - list + - watch + - apiGroups: + - events.k8s.io + resources: + - events + verbs: + - get + - list + - watch +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-config-map-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-config-map-template.yaml new file mode 100644 index 000000000..20033422d --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-config-map-template.yaml @@ -0,0 +1,35 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "cost-analyzer.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + {{- if .Values.global.prometheus.enabled }} + {{- if .Values.global.zone }} + prometheus-alertmanager-endpoint: http://{{ template "cost-analyzer.prometheus.alertmanager.name" . }}.{{ .Release.Namespace }}.svc.{{ .Values.global.zone }} + {{ else }} + prometheus-alertmanager-endpoint: http://{{ template "cost-analyzer.prometheus.alertmanager.name" . }}.{{ .Release.Namespace }} + {{- end -}} + {{ else }} + prometheus-alertmanager-endpoint: {{ .Values.global.notifications.alertmanager.fqdn }} + {{- end -}} + {{ if .Values.global.gmp.enabled }} + prometheus-server-endpoint: {{ .Values.global.gmp.prometheusServerEndpoint }} + {{- else if .Values.global.amp.enabled }} + prometheus-server-endpoint: {{ .Values.global.amp.prometheusServerEndpoint }} + {{- else if .Values.global.prometheus.enabled }} + {{- if .Values.global.zone }} + prometheus-server-endpoint: http://{{ template "cost-analyzer.prometheus.server.name" . }}.{{ .Release.Namespace }}.svc.{{ .Values.global.zone }} + {{ else }} + prometheus-server-endpoint: http://{{ template "cost-analyzer.prometheus.server.name" . }}.{{ .Release.Namespace }} + {{- end -}} + {{ else }} + prometheus-server-endpoint: {{ .Values.global.prometheus.fqdn }} + {{- end -}} + {{- if .Values.kubecostToken }} + kubecost-token: {{ .Values.kubecostToken }} + {{ else }} + kubecost-token: not-applied + {{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-db-pvc-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-db-pvc-template.yaml new file mode 100644 index 000000000..9b81ee367 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-db-pvc-template.yaml @@ -0,0 +1,35 @@ +{{- if .Values.persistentVolume -}} +{{- if not .Values.persistentVolume.dbExistingClaim -}} +{{- if .Values.persistentVolume.enabled -}} +{{- if .Values.persistentVolume.dbPVEnabled -}} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "cost-analyzer.fullname" . }}-db + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- with .Values.persistentVolume.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.persistentVolume.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + accessModes: + - ReadWriteOnce + {{- if .Values.persistentVolume.dbStorageClass }} + storageClassName: {{ .Values.persistentVolume.dbStorageClass }} + {{ end }} + resources: + requests: + {{- if .Values.persistentVolume }} + storage: {{ .Values.persistentVolume.dbSize }} + {{- else }} + storage: 32.0Gi + {{ end }} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-deployment-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-deployment-template.yaml new file mode 100644 index 000000000..7d9d2ee46 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-deployment-template.yaml @@ -0,0 +1,1191 @@ +{{- if and (not .Values.agent) (not .Values.cloudAgent) }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "cost-analyzer.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if and .Values.kubecostDeployment .Values.kubecostDeployment.labels }} + {{- toYaml .Values.kubecostDeployment.labels | nindent 4 }} + {{- end }} + {{- if and .Values.kubecostDeployment .Values.kubecostDeployment.annotations }} + annotations: + {{- toYaml .Values.kubecostDeployment.annotations | nindent 4 }} + {{- end }} +spec: +{{- if .Values.kubecostDeployment }} + replicas: {{ .Values.kubecostDeployment.replicas | default 1 }} +{{- end }} + selector: + matchLabels: + {{- include "cost-analyzer.selectorLabels" . | nindent 8}} +{{- if .Values.kubecostDeployment }} +{{- if .Values.kubecostDeployment.deploymentStrategy }} +{{- with .Values.kubecostDeployment.deploymentStrategy }} + strategy: {{ toYaml . | nindent 4 }} +{{- end }} +{{- else }} + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate +{{- end }} +{{- end }} + template: + metadata: + labels: + {{- include "cost-analyzer.selectorLabels" . | nindent 8 }} + {{/* Force pod restarts on upgrades to ensure the nginx config is current */}} + {{- if not .Values.global.platforms.cicd.enabled }} + helm-rollout-restarter: {{ randAlphaNum 5 | quote }} + {{- end }} + {{- if .Values.global.additionalLabels }} + {{ toYaml .Values.global.additionalLabels | nindent 8 }} + {{- end }} + {{- if and .Values.kubecostDeployment .Values.kubecostDeployment.labels }} + {{- toYaml .Values.kubecostDeployment.labels | nindent 8 }} + {{- end }} + {{- with .Values.global.podAnnotations}} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- if .Values.global.platforms.openshift.enabled }} + securityContext: + {{- toYaml .Values.global.platforms.openshift.securityContext | nindent 8 }} + {{- else if .Values.global.securityContext }} + securityContext: + {{- toYaml .Values.global.securityContext | nindent 8 }} + {{- else }} + securityContext: + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + {{- end }} + restartPolicy: Always + serviceAccountName: {{ template "cost-analyzer.serviceAccountName" . }} + volumes: + {{- if .Values.kubecostModel.plugins.enabled }} + - name: plugins-dir + emptyDir: {} + {{- if and (not .Values.kubecostModel.plugins.existingCustomSecret.enabled) .Values.kubecostModel.plugins.secretName }} + - name: plugins-config + secret: + secretName: {{ .Values.kubecostModel.plugins.secretName }} + items: + - key: datadog_config.json + path: datadog_config.json + {{- end }} + {{- if .Values.kubecostModel.plugins.existingCustomSecret.enabled }} + - name: plugins-config + secret: + secretName: {{ .Values.kubecostModel.plugins.existingCustomSecret.name }} + items: + - key: datadog_config.json + path: datadog_config.json + {{- end }} + {{- if .Values.kubecostModel.plugins.install.enabled}} + - name: install-script + configMap: + name: {{ template "cost-analyzer.fullname" . }}-install-plugins + {{- end }} + {{- end }} + {{- if .Values.global.gcpstore.enabled }} + - name: ubbagent-config + configMap: + name: ubbagent-config + {{- end }} + {{- if .Values.hosted }} + - name: config-store + secret: + defaultMode: 420 + secretName: kubecost-thanos + {{- end }} + - name: tmp + emptyDir: {} + {{- if and .Values.kubecostFrontend.enabled (not .Values.federatedETL.agentOnly) (not (eq (include "frontend.deployMethod" .) "haMode")) }} + - name: nginx-conf + configMap: + name: nginx-conf + items: + - key: nginx.conf + path: default.conf + {{- end }} + {{- if .Values.global.containerSecuritycontext }} + - name: var-run + emptyDir: { } + - name: cache + emptyDir: { } + {{- end }} + {{- /* + To opt out of ETL backups, set .Values.kubecostModel.etlBucketConfigSecret="" + */}} + {{- $etlBackupBucketSecret := "" }} + {{- if .Values.kubecostModel.etlBucketConfigSecret }} + {{- $etlBackupBucketSecret = .Values.kubecostModel.etlBucketConfigSecret }} + {{- end }} + {{- if $etlBackupBucketSecret }} + - name: etl-bucket-config + secret: + defaultMode: 420 + secretName: {{ $etlBackupBucketSecret }} + {{- end }} + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: federated-storage-config + secret: + defaultMode: 420 + secretName: {{ .Values.kubecostModel.federatedStorageConfigSecret }} + {{- end }} + {{- if .Values.kubecostProductConfigs }} + {{- if and ((.Values.kubecostProductConfigs).productKey).enabled ((.Values.kubecostProductConfigs).productKey).secretname }} + - name: productkey-secret + secret: + secretName: {{ .Values.kubecostProductConfigs.productKey.secretname }} + items: + - key: productkey.json + path: productkey.json + {{- end }} + {{- if ((.Values.kubecostProductConfigs).smtp).secretname }} + - name: smtp-secret + secret: + secretName: {{ .Values.kubecostProductConfigs.smtp.secretname }} + items: + - key: smtp.json + path: smtp.json + {{- end }} + {{- if .Values.kubecostProductConfigs }} + {{- if .Values.kubecostProductConfigs.gcpSecretName }} + - name: gcp-key-secret + secret: + secretName: {{ .Values.kubecostProductConfigs.gcpSecretName }} + items: + - key: {{ .Values.kubecostProductConfigs.gcpSecretKeyName | default "compute-viewer-kubecost-key.json" }} + path: service-key.json + {{- end }} + {{- end -}} + {{- if .Values.kubecostProductConfigs.serviceKeySecretName }} + - name: service-key-secret + secret: + secretName: {{ .Values.kubecostProductConfigs.serviceKeySecretName }} + {{- else if .Values.kubecostProductConfigs.createServiceKeySecret }} + - name: service-key-secret + secret: + secretName: cloud-service-key + {{- end }} + {{- if .Values.kubecostProductConfigs.azureStorageSecretName }} + - name: azure-storage-config + secret: + secretName: {{ .Values.kubecostProductConfigs.azureStorageSecretName }} + items: + - key: azure-storage-config.json + path: azure-storage-config.json + {{- else if .Values.kubecostProductConfigs.azureStorageCreateSecret }} + - name: azure-storage-config + secret: + secretName: azure-storage-config + {{- end }} + {{- if .Values.kubecostProductConfigs.cloudIntegrationSecret }} + - name: cloud-integration + secret: + secretName: {{ .Values.kubecostProductConfigs.cloudIntegrationSecret }} + items: + - key: cloud-integration.json + path: cloud-integration.json + {{- else if or .Values.kubecostProductConfigs.cloudIntegrationJSON ((.Values.kubecostProductConfigs).athenaBucketName) }} + - name: cloud-integration + secret: + secretName: cloud-integration + items: + - key: cloud-integration.json + path: cloud-integration.json + {{- end }} + {{- if .Values.kubecostProductConfigs.clusters }} + - name: kubecost-clusters + configMap: + name: kubecost-clusters + {{- range .Values.kubecostProductConfigs.clusters }} + {{- if .auth }} + {{- if .auth.secretName }} + - name: {{ .auth.secretName }} + secret: + secretName: {{ .auth.secretName }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.kubecostFrontend.tls }} + {{- if .Values.kubecostFrontend.tls.enabled }} + - name: tls + secret: + secretName : {{ .Values.kubecostFrontend.tls.secretName }} + items: + - key: tls.crt + path: kc.crt + - key: tls.key + path: kc.key + {{- end }} + {{- end }} + {{- if .Values.kubecostAdmissionController }} + {{- if .Values.kubecostAdmissionController.enabled }} + {{- if .Values.kubecostAdmissionController.secretName }} + - name: webhook-server-tls + secret: + secretName: {{ .Values.kubecostAdmissionController.secretName }} + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.saml }} + {{- if .Values.saml.enabled }} + {{- if .Values.saml.secretName }} + - name: secret-volume + secret: + secretName: {{ .Values.saml.secretName }} + {{- end }} + {{- if .Values.saml.encryptionCertSecret }} + - name: saml-encryption-cert + secret: + secretName: {{ .Values.saml.encryptionCertSecret }} + {{- end }} + {{- if .Values.saml.decryptionKeySecret }} + - name: saml-decryption-key + secret: + secretName: {{ .Values.saml.decryptionKeySecret }} + {{- end }} + {{- if .Values.saml.metadataSecretName }} + - name: metadata-secret-volume + secret: + secretName: {{ .Values.saml.metadataSecretName }} + {{- end }} + - name: saml-auth-secret + secret: + secretName: {{ .Values.saml.authSecretName | default "kubecost-saml-secret" }} + {{- if .Values.saml.rbac.enabled }} + - name: saml-roles + configMap: + name: {{ template "cost-analyzer.fullname" . }}-saml + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.oidc }} + {{- if .Values.oidc.enabled }} + - name: oidc-config + configMap: + name: {{ template "cost-analyzer.fullname" . }}-oidc + {{- if and (not .Values.oidc.existingCustomSecret.enabled) .Values.oidc.secretName }} + - name: oidc-client-secret + secret: + secretName: {{ .Values.oidc.secretName }} + {{- end }} + {{- if .Values.oidc.existingCustomSecret.enabled }} + - name: oidc-client-secret + secret: + secretName: {{ .Values.oidc.existingCustomSecret.name }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.extraVolumes }} + # Extra volume(s) + {{- toYaml .Values.extraVolumes | nindent 8 }} + {{- end }} + - name: persistent-configs +{{- if .Values.persistentVolume }} +{{- if .Values.persistentVolume.enabled }} + persistentVolumeClaim: +{{- if .Values.persistentVolume.existingClaim }} + claimName: {{ .Values.persistentVolume.existingClaim }} +{{- else }} + claimName: {{ template "cost-analyzer.fullname" . }} +{{- end -}} +{{- else }} + emptyDir: {} +{{- end -}} +{{- else }} + persistentVolumeClaim: + claimName: {{ template "cost-analyzer.fullname" . }} +{{- end }} +{{- if .Values.persistentVolume.dbPVEnabled }} + - name: persistent-db +{{- if .Values.persistentVolume }} +{{- if .Values.persistentVolume.enabled }} + persistentVolumeClaim: +{{- if .Values.persistentVolume.dbExistingClaim }} + claimName: {{ .Values.persistentVolume.dbExistingClaim }} +{{- else }} + claimName: {{ template "cost-analyzer.fullname" . }}-db +{{- end -}} +{{- else }} + emptyDir: {} +{{- end -}} +{{- else }} + persistentVolumeClaim: + claimName: {{ template "cost-analyzer.fullname" . }}-db +{{- end }} +{{- end }} + initContainers: + {{- if and .Values.kubecostModel.plugins.enabled (not (eq (include "aggregator.deployMethod" .) "statefulset")) }} + - name: plugin-installer + image: {{ .Values.kubecostModel.plugins.install.fullImageName }} + command: ["sh", "/install/install_plugins.sh"] + {{- with .Values.kubecostModel.plugins.install.securityContext }} + securityContext: {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: install-script + mountPath: /install + - name: plugins-dir + mountPath: {{ .Values.kubecostModel.plugins.folder }} + {{- end }} + {{- if .Values.supportNFS }} + - name: config-db-perms-fix + {{- if .Values.initChownDataImage }} + image: {{ .Values.initChownDataImage }} + {{- else }} + image: busybox + {{- end }} + {{- with .Values.initChownData.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.persistentVolume.dbPVEnabled }} + command: ["sh", "-c", "/bin/chmod -R 777 /var/configs && /bin/chmod -R 777 /var/db"] + {{- else }} + command: ["sh", "-c", "/bin/chmod -R 777 /var/configs"] + {{- end }} + volumeMounts: + - name: persistent-configs + mountPath: /var/configs + {{- if .Values.persistentVolume.dbPVEnabled }} + - name: persistent-db + mountPath: /var/db + {{- end }} + securityContext: + runAsUser: 0 +{{ end }} + containers: + {{- if .Values.global.gmp.enabled }} + - name: {{ .Values.global.gmp.gmpProxy.name }} + image: {{ .Values.global.gmp.gmpProxy.image }} + {{- if .Values.global.gmp.gmpProxy.imagePullPolicy }} + imagePullPolicy: {{ .Values.global.gmp.gmpProxy.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + args: + - "--web.listen-address=:{{ .Values.global.gmp.gmpProxy.port }}" + - "--query.project-id={{ .Values.global.gmp.gmpProxy.projectId }}" + {{- if .Values.systemProxy.enabled }} + env: + - name: HTTP_PROXY + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: http_proxy + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: HTTPS_PROXY + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: https_proxy + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: NO_PROXY + value: {{ .Values.systemProxy.noProxy }} + - name: no_proxy + value: {{ .Values.systemProxy.noProxy }} + {{- end }} + ports: + - name: web + containerPort: {{ .Values.global.gmp.gmpProxy.port | int }} + readinessProbe: + httpGet: + path: /-/ready + port: web + livenessProbe: + httpGet: + path: /-/healthy + port: web + {{- end }} + {{- if .Values.global.amp.enabled }} + - name: sigv4proxy + image: {{ .Values.sigV4Proxy.image }} + {{- if .Values.sigV4Proxy.imagePullPolicy }} + imagePullPolicy: {{ .Values.sigV4Proxy.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + {{- if .Values.global.containerSecurityContext }} + securityContext: + {{- toYaml .Values.global.containerSecurityContext | nindent 12 -}} + {{- end }} + {{- with .Values.sigV4Proxy.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + args: + - --name + - {{ .Values.sigV4Proxy.name }} + - --region + - {{ .Values.sigV4Proxy.region }} + - --host + - {{ .Values.sigV4Proxy.host }} + {{- if .Values.sigV4Proxy.role_arn }} + - --role-arn + - {{ .Values.sigV4Proxy.role_arn }} + {{- end }} + - --port + - :{{ .Values.sigV4Proxy.port }} + ports: + - name: aws-sigv4-proxy + containerPort: {{ .Values.sigV4Proxy.port | int }} + env: + - name: AGENT_LOCAL_PORT + value: "{{ .Values.sigV4Proxy.port | int }}" + {{- if .Values.systemProxy.enabled }} + - name: HTTP_PROXY + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: http_proxy + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: HTTPS_PROXY + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: https_proxy + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: NO_PROXY + value: {{ .Values.systemProxy.noProxy }} + - name: no_proxy + value: {{ .Values.systemProxy.noProxy }} + {{- end }} + {{- if .Values.sigV4Proxy.extraEnv }} + {{- toYaml .Values.sigV4Proxy.extraEnv | nindent 10 }} + {{- end }} + {{- end }} + {{- if .Values.global.gcpstore.enabled }} + - name: ubbagent + image: gcr.io/kubecost1/gcp-mp/ent/cost-model/ubbagent:1.0 + env: + {{- if .Values.systemProxy.enabled }} + - name: HTTP_PROXY + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: http_proxy + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: HTTPS_PROXY + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: https_proxy + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: NO_PROXY + value: {{ .Values.systemProxy.noProxy }} + - name: no_proxy + value: {{ .Values.systemProxy.noProxy }} + {{- end }} + - name: AGENT_CONFIG_FILE + value: "/etc/ubbagent/config.yaml" + - name: AGENT_LOCAL_PORT + value: "6080" + - name: AGENT_ENCODED_KEY + valueFrom: + secretKeyRef: + name: {{ default "kubecost-reporting-secret" .Values.reportingSecret }} + key: reporting-key + - name: AGENT_CONSUMER_ID + valueFrom: + secretKeyRef: + name: {{ default "kubecost-reporting-secret" .Values.reportingSecret }} + key: consumer-id + volumeMounts: + - name: ubbagent-config + mountPath: /etc/ubbagent + {{- end }} + {{- if .Values.kubecostModel }} + {{- if .Values.kubecostModel.fullImageName }} + - image: {{ .Values.kubecostModel.fullImageName }} + {{- else if .Values.imageVersion }} + - image: {{ .Values.kubecostModel.image }}:{{ .Values.imageVersion }} + {{- else if eq "development" .Chart.AppVersion }} + - image: gcr.io/kubecost1/cost-model-nightly:latest + {{- else }} + - image: {{ .Values.kubecostModel.image }}:prod-{{ $.Chart.AppVersion }} + {{- end }} + {{- else }} + - image: gcr.io/kubecost1/cost-model:prod-{{ $.Chart.AppVersion }} + {{- end }} + name: cost-model + {{- if .Values.kubecostModel.extraArgs }} + args: + {{- toYaml .Values.kubecostModel.extraArgs | nindent 12 }} + {{- end }} + securityContext: + {{- if .Values.kubecostModel.securityContext }} + {{- toYaml .Values.kubecostModel.securityContext | nindent 12 -}} + {{- else if .Values.global.containerSecurityContext }} + {{- toYaml .Values.global.containerSecurityContext | nindent 12 -}} + {{- end }} + {{- if .Values.kubecostModel.imagePullPolicy }} + imagePullPolicy: {{ .Values.kubecostModel.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + ports: + - name: tcp-model + containerPort: 9003 + protocol: TCP + {{- if and .Values.kubecostFrontend.enabled (not .Values.federatedETL.agentOnly) (not (eq (include "frontend.deployMethod" .) "haMode")) }} + - name: tcp-frontend + containerPort: 9090 + protocol: TCP + {{- end }} + {{- with .Values.kubecostModel.extraPorts }} + {{- toYaml . | nindent 10 }} + {{- end }} + resources: +{{ toYaml .Values.kubecostModel.resources | indent 12 }} + {{- if .Values.kubecostModel.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: /healthz + port: 9003 + initialDelaySeconds: {{ .Values.kubecostModel.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.kubecostModel.readinessProbe.periodSeconds}} + failureThreshold: {{ .Values.kubecostModel.readinessProbe.failureThreshold}} + {{- end }} + {{- if .Values.kubecostModel.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: /healthz + port: 9003 + initialDelaySeconds: {{ .Values.kubecostModel.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.kubecostModel.livenessProbe.periodSeconds }} + failureThreshold: {{ .Values.kubecostModel.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.global.containerSecuritycontext }} + securityContext: + {{- toYaml .Values.global.containerSecuritycontext | nindent 12 }} + {{- end }} + volumeMounts: + {{- if .Values.hosted }} + - name: config-store + mountPath: /var/secrets + readOnly: true + {{- end }} + - name: persistent-configs + mountPath: /var/configs + {{- if .Values.extraVolumeMounts }} + # Extra volume mount(s) + {{- toYaml .Values.extraVolumeMounts | nindent 12 }} + {{- end }} + {{- if $etlBackupBucketSecret }} + - name: etl-bucket-config + mountPath: /var/configs/etl + readOnly: true + {{- else if .Values.persistentVolume.dbPVEnabled }} + - name: persistent-db + mountPath: /var/db + {{- end }} + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: federated-storage-config + mountPath: /var/configs/etl/federated + readOnly: true + {{- end }} + {{- if .Values.kubecostAdmissionController }} + {{- if .Values.kubecostAdmissionController.enabled }} + {{- if .Values.kubecostAdmissionController.secretName }} + - name: {{ .Values.kubecostAdmissionController.secretName }} + mountPath: /certs + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.kubecostProductConfigs }} + {{- if and ((.Values.kubecostProductConfigs).productKey).enabled ((.Values.kubecostProductConfigs).productKey).secretname }} + - name: productkey-secret + mountPath: /var/configs/productkey + {{- end }} + {{- if ((.Values.kubecostProductConfigs).smtp).secretname }} + - name: smtp-secret + mountPath: /var/configs/smtp + {{- end }} + {{- if .Values.kubecostProductConfigs.gcpSecretName }} + - name: gcp-key-secret + mountPath: /var/secrets + {{- end }} + {{- if or .Values.kubecostProductConfigs.azureStorageSecretName .Values.kubecostProductConfigs.azureStorageCreateSecret }} + - name: azure-storage-config + mountPath: /var/azure-storage-config + {{- end }} + {{- if or .Values.kubecostProductConfigs.serviceKeySecretName .Values.kubecostProductConfigs.createServiceKeySecret }} + - name: service-key-secret + mountPath: /var/secrets + {{- end }} + {{- if .Values.kubecostProductConfigs.clusters }} + - name: kubecost-clusters + mountPath: /var/configs/clusters + {{- range .Values.kubecostProductConfigs.clusters }} + {{- if .auth }} + {{- if .auth.secretName }} + - name: {{ .auth.secretName }} + mountPath: /var/secrets/{{ .auth.secretName }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.saml }} + {{- if .Values.saml.enabled }} + {{- if .Values.saml.secretName }} + - name: secret-volume + mountPath: /var/configs/secret-volume + {{- end }} + {{- if .Values.saml.encryptionCertSecret }} + - name: saml-encryption-cert + mountPath: /var/configs/saml-encryption-cert + {{- end }} + {{- if .Values.saml.decryptionKeySecret }} + - name: saml-decryption-key + mountPath: /var/configs/saml-decryption-key + {{- end }} + {{- if .Values.saml.metadataSecretName }} + - name: metadata-secret-volume + mountPath: /var/configs/metadata-secret-volume + {{- end }} + - name: saml-auth-secret + mountPath: /var/configs/saml-auth-secret + {{- if .Values.saml.rbac.enabled }} + - name: saml-roles + mountPath: /var/configs/saml + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.oidc }} + {{- if .Values.oidc.enabled }} + - name: oidc-config + mountPath: /var/configs/oidc + {{- if or .Values.oidc.existingCustomSecret.name .Values.oidc.secretName }} + - name: oidc-client-secret + mountPath: /var/configs/oidc-client-secret + {{- end }} + {{- end }} + {{- end }} + env: + {{- if .Values.global.grafana }} + - name: GRAFANA_ENABLED + value: "{{ template "cost-analyzer.grafanaEnabled" . }}" + {{- end}} + {{- if .Values.kubecostModel.extraEnv -}} + {{ toYaml .Values.kubecostModel.extraEnv | nindent 12 }} + {{- end }} + {{- if .Values.reporting }} + {{- if .Values.reporting.valuesReporting }} + - name: HELM_VALUES + value: {{ template "cost-analyzer.filterEnabled" .Values }} + {{- end }} + {{- end }} + {{- if .Values.alertConfigmapName }} + - name: ALERT_CONFIGMAP_NAME + value: {{ .Values.alertConfigmapName }} + {{- end }} + {{- if .Values.productConfigmapName }} + - name: PRODUCT_CONFIGMAP_NAME + value: {{ .Values.productConfigmapName }} + {{- end }} + {{- if .Values.smtpConfigmapName }} + - name: SMTP_CONFIGMAP_NAME + value: {{ .Values.smtpConfigmapName }} + {{- end }} + {{- if .Values.appConfigmapName }} + - name: APP_CONFIGMAP_NAME + value: {{ .Values.appConfigmapName }} + {{- end }} + {{- if .Values.kubecostModel.softMemoryLimit }} + - name: GOMEMLIMIT + value: {{ .Values.kubecostModel.softMemoryLimit }} + {{- end }} + {{- if .Values.assetReportConfigmapName }} + - name: ASSET_REPORT_CONFIGMAP_NAME + value: {{ .Values.assetReportConfigmapName }} + {{- end }} + {{- if .Values.cloudCostReportConfigmapName }} + - name: CLOUD_COST_REPORT_CONFIGMAP_NAME + value: {{ .Values.cloudCostReportConfigmapName }} + {{- end }} + {{- if .Values.savedReportConfigmapName }} + - name: SAVED_REPORT_CONFIGMAP_NAME + value: {{ .Values.savedReportConfigmapName }} + {{- end }} + {{- if .Values.groupFiltersConfigmapName }} + - name: GROUP_FILTERS_CONFIGMAP_NAME + value: {{ .Values.groupFiltersConfigmapName }} + {{- end }} + {{- if .Values.pricingConfigmapName }} + - name: PRICING_CONFIGMAP_NAME + value: {{ .Values.pricingConfigmapName }} + {{- end }} + {{- if .Values.metricsConfigmapName }} + - name: METRICS_CONFIGMAP_NAME + value: {{ .Values.metricsConfigmapName }} + {{- end }} + - name: READ_ONLY + value: {{ (quote .Values.readonly) | default (quote false) }} + - name: PROMETHEUS_SERVER_ENDPOINT + valueFrom: + configMapKeyRef: + name: {{ template "cost-analyzer.fullname" . }} + key: prometheus-server-endpoint + - name: CLOUD_PROVIDER_API_KEY + value: "AIzaSyDXQPG_MHUEy9neR7stolq6l0ujXmjJlvk" # The GCP Pricing API key.This GCP api key is expected to be here and is limited to accessing google's billing API. + {{- if .Values.kubecostProductConfigs }} + {{- if .Values.kubecostProductConfigs.gcpSecretName }} + - name: GOOGLE_APPLICATION_CREDENTIALS + value: /var/configs/key.json + {{- end }} + {{- end }} + - name: CONFIG_PATH + value: /var/configs/ + - name: DB_PATH + value: /var/db/ + - name: CLUSTER_PROFILE + {{- if .Values.kubecostProductConfigs }} + value: {{ .Values.kubecostProductConfigs.clusterProfile | default "production" }} + {{- else }} + value: production + {{- end }} + {{- if .Values.kubecostProductConfigs }} + {{- if ((.Values.kubecostProductConfigs).productKey).mountPath }} + - name: PRODUCT_KEY_MOUNT_PATH + value: {{ .Values.kubecostProductConfigs.productKey.mountPath }} + {{- end }} + {{- if ((.Values.kubecostProductConfigs).smtp).mountPath }} + - name: SMTP_CONFIG_MOUNT_PATH + value: {{ .Values.kubecostProductConfigs.smtp.mountPath }} + {{- end }} + {{- if .Values.kubecostProductConfigs.ingestPodUID }} + - name: INGEST_POD_UID + value: {{ (quote .Values.kubecostProductConfigs.ingestPodUID) }} + {{- end }} + {{- if .Values.kubecostProductConfigs.regionOverrides }} + - name: REGION_OVERRIDE_LIST + value: {{ (quote .Values.kubecostProductConfigs.regionOverrides) }} + {{- end }} + {{- end }} + {{- if .Values.global.prometheus.queryServiceBasicAuthSecretName}} + - name: DB_BASIC_AUTH_USERNAME + valueFrom: + secretKeyRef: + name: {{ .Values.global.prometheus.queryServiceBasicAuthSecretName }} + key: USERNAME + - name: DB_BASIC_AUTH_PW + valueFrom: + secretKeyRef: + name: {{ .Values.global.prometheus.queryServiceBasicAuthSecretName }} + key: PASSWORD + {{- end }} + {{- if .Values.global.prometheus.queryServiceBearerTokenSecretName }} + - name: DB_BEARER_TOKEN + valueFrom: + secretKeyRef: + name: {{ .Values.global.prometheus.queryServiceBearerTokenSecretName }} + key: TOKEN + {{- end }} + {{- if .Values.global.prometheus.insecureSkipVerify }} + - name: INSECURE_SKIP_VERIFY + value: {{ (quote .Values.global.prometheus.insecureSkipVerify) }} + {{- end }} + {{- if .Values.pricingCsv }} + {{- if .Values.pricingCsv.enabled }} + - name: USE_CSV_PROVIDER + value: "true" + - name: CSV_PATH + value: {{ .Values.pricingCsv.location.URI }} + - name: CSV_REGION + value: {{ .Values.pricingCsv.location.region }} + {{- if eq .Values.pricingCsv.location.provider "AWS"}} + {{- if .Values.pricingCsv.location.csvAccessCredentials }} + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: {{ .Values.pricingCsv.location.csvAccessCredentials }} + key: AWS_ACCESS_KEY_ID + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ .Values.pricingCsv.location.csvAccessCredentials }} + key: AWS_SECRET_ACCESS_KEY + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.kubecostMetrics }} + - name: EMIT_POD_ANNOTATIONS_METRIC + value: {{ (quote .Values.kubecostMetrics.emitPodAnnotations) | default (quote false) }} + - name: EMIT_NAMESPACE_ANNOTATIONS_METRIC + value: {{ (quote .Values.kubecostMetrics.emitNamespaceAnnotations) | default (quote false) }} + {{- end }} + {{- if .Values.kubecostMetrics }} + - name: EMIT_KSM_V1_METRICS + value: {{ (quote .Values.kubecostMetrics.emitKsmV1Metrics) | default (quote true) }} + {{- end }} + {{- if .Values.kubecostMetrics }} + - name: EMIT_KSM_V1_METRICS_ONLY # ONLY emit KSM v1 metrics that do not exist in KSM 2 by default + value: {{ (quote .Values.kubecostMetrics.emitKsmV1MetricsOnly) | default (quote false) }} + {{- end }} + {{- if .Values.reporting }} + - name: LOG_COLLECTION_ENABLED + value: {{ (quote .Values.reporting.logCollection) | default (quote true) }} + - name: PRODUCT_ANALYTICS_ENABLED + value: {{ (quote .Values.reporting.productAnalytics) | default (quote true) }} + - name: ERROR_REPORTING_ENABLED + value: {{ (quote .Values.reporting.errorReporting ) | default (quote true) }} + - name: VALUES_REPORTING_ENABLED + value: {{ (quote .Values.reporting.valuesReporting) | default (quote true) }} + {{- if .Values.reporting.errorReporting }} + - name: SENTRY_DSN + value: "https://71964476292e4087af8d5072afe43abd@o394722.ingest.sentry.io/5245431" + {{- end }} + {{- end }} + - name: LEGACY_EXTERNAL_API_DISABLED + value: {{ (quote .Values.kubecostModel.legacyOutOfClusterAPIDisabled) | default (quote false) }} + - name: CACHE_WARMING_ENABLED + value: {{ (quote .Values.kubecostModel.warmCache) | default (quote true) }} + - name: SAVINGS_ENABLED + value: {{ (quote .Values.kubecostModel.warmSavingsCache) | default (quote true) }} + {{- if $etlBackupBucketSecret }} + - name: ETL_BUCKET_CONFIG + value: "/var/configs/etl/object-store.yaml" + {{- end }} + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: FEDERATED_STORE_CONFIG + value: "/var/configs/etl/federated/federated-store.yaml" + {{- end }} + {{- if or .Values.federatedETL.federatedCluster .Values.kubecostModel.federatedStorageConfigSecret }} + - name: FEDERATED_CLUSTER + {{- if eq .Values.federatedETL.readOnlyPrimary true }} + value: "false" + {{- else }} + value: "true" + {{- end }} + {{- end }} + {{- if .Values.federatedETL.redirectS3Backup }} + - name: FEDERATED_REDIRECT_BACKUP + value: "true" + {{- end }} + {{- if .Values.federatedETL.useMultiClusterDB }} + - name: CURRENT_CLUSTER_ID_FILTER_ENABLED + value: "true" + {{- end }} + {{- if .Values.persistentVolume.dbPVEnabled }} + - name: ETL_PATH_PREFIX + value: "/var/db" + {{- end }} + - name: ETL_RESOLUTION_SECONDS + value: {{ (quote .Values.kubecostModel.etlResolutionSeconds) | default (quote 300) }} + - name: ETL_MAX_PROMETHEUS_QUERY_DURATION_MINUTES + value: {{ (quote .Values.kubecostModel.maxPrometheusQueryDurationMinutes) | default (quote 1440) }} + - name: ETL_DAILY_STORE_DURATION_DAYS + value: {{ (quote .Values.kubecostModel.etlDailyStoreDurationDays) }} + - name: ETL_HOURLY_STORE_DURATION_HOURS + value: {{ (quote .Values.kubecostModel.etlHourlyStoreDurationHours) | default (quote 49) }} + - name: ETL_WEEKLY_STORE_DURATION_WEEKS + value: {{ (quote .Values.kubecostModel.etlWeeklyStoreDurationWeeks) | default (quote 53) }} + - name: ETL_FILE_STORE_ENABLED + value: {{ (quote .Values.kubecostModel.etlFileStoreEnabled) | default (quote true) }} + - name: ETL_ASSET_RECONCILIATION_ENABLED + value: {{ (quote .Values.kubecostModel.etlAssetReconciliationEnabled) | default (quote true) }} + + {{- if .Values.kubecostModel }} + {{- if .Values.kubecostModel.allocation }} + {{- if .Values.kubecostModel.allocation.nodeLabels }} + {{- with .Values.kubecostModel.allocation.nodeLabels }} + - name: ALLOCATION_NODE_LABELS_ENABLED + value: {{ (quote .enabled) | default (quote true) }} + - name: ALLOCATION_NODE_LABELS_INCLUDE_LIST + value: {{ (quote .includeList) }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + - name: CONTAINER_STATS_ENABLED + value: {{ (quote .Values.kubecostModel.containerStatsEnabled) | default (quote false) }} + - name: RECONCILE_NETWORK + value: {{ (quote .Values.kubecostModel.reconcileNetwork) | default (quote true) }} + {{- if .Values.systemProxy.enabled }} + - name: HTTP_PROXY + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: http_proxy + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: HTTPS_PROXY + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: https_proxy + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: NO_PROXY + value: {{ .Values.systemProxy.noProxy }} + - name: no_proxy + value: {{ .Values.systemProxy.noProxy }} + {{- end }} + {{- if .Values.kubecostMetrics }} + {{- if .Values.kubecostMetrics.exporter }} + - name: KUBECOST_METRICS_POD_ENABLED + value: {{ (quote .Values.kubecostMetrics.exporter.enabled) | default (quote false) }} + {{- end }} + {{- end }} + - name: PV_ENABLED + value: {{ (quote .Values.persistentVolume.enabled) | default (quote true) }} + - name: MAX_QUERY_CONCURRENCY + value: {{ (quote .Values.kubecostModel.maxQueryConcurrency) | default (quote 5) }} + - name: UTC_OFFSET + value: {{ (quote .Values.kubecostModel.utcOffset) | default (quote ) }} + {{- if .Values.networkCosts }} + {{- if .Values.networkCosts.enabled }} + - name: NETWORK_COSTS_PORT + value: {{ quote .Values.networkCosts.port | default (quote 3001) }} + # ADVANCED_NETWORK_STATS is a feature offered by Kubecost that gives you network + # insights of your Kubernetes resources with cloud services. The feature is + # enabled when network cost is enabled and one of the service tagging is enabled + {{- if .Values.networkCosts.config.services }} + {{- $services := .Values.networkCosts.config.services -}} + {{- if or (index $services "google-cloud-services") (index $services "amazon-web-services") (index $services "azure-cloud-services")}} + - name: ADVANCED_NETWORK_STATS + value: "true" + {{- else}} + - name: ADVANCED_NETWORK_STATS + value: "false" + {{- end}} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.oidc.enabled }} + - name: OIDC_ENABLED + value: "true" + - name: OIDC_SKIP_ONLINE_VALIDATION + value: {{ (quote .Values.oidc.skipOnlineTokenValidation) | default (quote false) }} + {{- end}} + {{- if .Values.saml }} + {{- if .Values.saml.enabled }} + - name: SAML_ENABLED + value: "true" + - name: IDP_URL + value: {{ .Values.saml.idpMetadataURL }} + - name: SP_HOST + value: {{ .Values.saml.appRootURL }} + {{- if .Values.saml.audienceURI }} + - name: AUDIENCE_URI + value: {{ .Values.saml.audienceURI }} + {{- end }} + {{- if .Values.saml.isGLUUProvider }} + - name: GLUU_SAML_PROVIDER + value: {{ (quote .Values.saml.isGLUUProvider) }} + {{- end }} + {{- if .Values.saml.nameIDFormat }} + - name: NAME_ID_FORMAT + value: {{ .Values.saml.nameIDFormat }} + {{- end}} + {{- if .Values.saml.authTimeout }} + - name: AUTH_TOKEN_TIMEOUT + value: {{ (quote .Values.saml.authTimeout) }} + {{- end}} + {{- if .Values.saml.redirectURL }} + - name: LOGOUT_REDIRECT_URL + value: {{ .Values.saml.redirectURL }} + {{- end}} + {{- if .Values.saml.rbac.enabled }} + - name: SAML_RBAC_ENABLED + value: "true" + {{- end }} + {{- if and .Values.saml.encryptionCertSecret .Values.saml.decryptionKeySecret }} + - name: SAML_RESPONSE_ENCRYPTED + value: "true" + {{- end}} + {{- end }} + {{- end }} + {{- if and (.Values.prometheus.server.global.external_labels.cluster_id) (not .Values.prometheus.server.clusterIDConfigmap) }} + - name: CLUSTER_ID + value: {{ .Values.prometheus.server.global.external_labels.cluster_id }} + {{- end }} + {{- if .Values.prometheus.server.clusterIDConfigmap }} + - name: CLUSTER_ID + valueFrom: + configMapKeyRef: + name: {{ .Values.prometheus.server.clusterIDConfigmap }} + key: CLUSTER_ID + {{- end }} + {{- if .Values.kubecostModel.promClusterIDLabel }} + - name: PROM_CLUSTER_ID_LABEL + value: {{ .Values.kubecostModel.promClusterIDLabel }} + {{- end }} + {{- if .Values.hosted }} + - name: KUBECOST_CONFIG_BUCKET + value: /var/secrets/object-store.yaml + - name: CLUSTER_INFO_FILE_ENABLED + value: "true" + - name: CLUSTER_CACHE_FILE_ENABLED + value: "true" + {{- end }} + {{- if .Values.reporting.googleAnalyticsTag }} + - name: GOOGLE_ANALYTICS_TAG + value: {{ .Values.reporting.googleAnalyticsTag }} + {{- end }} + {{- if .Values.costEventsAudit }} + - name: COST_EVENTS_AUDIT_ENABLED + value: {{ (quote .Values.costEventsAudit.enabled) | default (quote false) }} + {{- end }} + - name: RELEASE_NAME + value: {{ .Release.Name }} + - name: KUBECOST_NAMESPACE + value: {{ .Release.Namespace }} + - name: POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: KUBECOST_TOKEN + valueFrom: + configMapKeyRef: + name: {{ template "cost-analyzer.fullname" . }} + key: kubecost-token + - name: WATERFOWL_ENABLED + value: "true" + {{- if not (.Values.diagnostics.enabled) }} + - name: DIAGNOSTICS_RUN_IN_COST_MODEL + value: "false" + {{- /*A pre-requisite for running MultiClusterDiagnostics in the cost-model container is a configured federated-store secret and cluster_id*/}} + {{- else if or (empty .Values.kubecostModel.federatedStorageConfigSecret) (eq .Values.prometheus.server.global.external_labels.cluster_id "cluster-one") }} + - name: DIAGNOSTICS_RUN_IN_COST_MODEL + value: "false" + {{- else if .Values.diagnostics.deployment.enabled }} + - name: DIAGNOSTICS_RUN_IN_COST_MODEL + value: "false" + {{- else }} + - name: DIAGNOSTICS_RUN_IN_COST_MODEL + value: "true" + - name: DIAGNOSTICS_KUBECOST_FQDN + value: "localhost" + - name: DIAGNOSTICS_KUBECOST_NAMESPACE + value: {{ .Release.Namespace }} + - name: DIAGNOSTICS_PRIMARY + value: {{ quote .Values.diagnostics.primary.enabled }} + - name: DIAGNOSTICS_RETENTION + value: {{ .Values.diagnostics.primary.retention }} + - name: DIAGNOSTICS_PRIMARY_READONLY + value: {{ quote .Values.diagnostics.primary.readonly }} + - name: DIAGNOSTICS_POLLING_INTERVAL + value: {{ .Values.diagnostics.pollingInterval }} + - name: DIAGNOSTICS_KEEP_HISTORY + value: {{ quote .Values.diagnostics.keepDiagnosticHistory }} + - name: DIAGNOSTICS_COLLECT_HELM_VALUES + value: {{ quote .Values.diagnostics.collectHelmValues }} + {{- end }} + {{- if and .Values.kubecostFrontend.enabled (not .Values.federatedETL.agentOnly) (not (eq (include "frontend.deployMethod" .) "haMode")) }} + {{- if .Values.kubecostFrontend }} + {{- if .Values.kubecostFrontend.fullImageName }} + - image: {{ .Values.kubecostFrontend.fullImageName }} + {{- else if .Values.imageVersion }} + - image: {{ .Values.kubecostFrontend.image }}:{{ .Values.imageVersion }} + {{- else if eq "development" .Chart.AppVersion }} + - image: gcr.io/kubecost1/frontend-nightly:latest + {{- else }} + - image: {{ .Values.kubecostFrontend.image }}:prod-{{ $.Chart.AppVersion }} + {{- end }} + {{- else }} + - image: gcr.io/kubecost1/frontend:prod-{{ $.Chart.AppVersion }} + {{- end }} + env: + - name: GET_HOSTS_FROM + value: dns + {{- if .Values.kubecostFrontend.extraEnv -}} + {{ toYaml .Values.kubecostFrontend.extraEnv | nindent 12 }} + {{- end }} + name: cost-analyzer-frontend + {{- if .Values.kubecostFrontend.securityContext }} + securityContext: + {{- toYaml .Values.kubecostFrontend.securityContext | nindent 12 }} + {{- else }} + securityContext: + {{- toYaml .Values.global.containerSecurityContext | nindent 12 }} + {{- end }} + volumeMounts: + - name: tmp + mountPath: /tmp + - name: nginx-conf + mountPath: /etc/nginx/conf.d/ + {{- if .Values.global.containerSecuritycontext }} + - mountPath: /var/cache/nginx + name: cache + - mountPath: /var/run + name: var-run + {{- end }} + {{- if .Values.kubecostFrontend.tls }} + {{- if .Values.kubecostFrontend.tls.enabled }} + - name: tls + mountPath: /etc/ssl/certs + {{- end }} + {{- end }} + resources: +{{ toYaml .Values.kubecostFrontend.resources | indent 12 }} + {{- if .Values.kubecostFrontend.imagePullPolicy }} + imagePullPolicy: {{ .Values.kubecostFrontend.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + {{- if .Values.kubecostFrontend.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: /healthz + port: 9003 + initialDelaySeconds: {{ .Values.kubecostFrontend.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.kubecostFrontend.readinessProbe.periodSeconds }} + failureThreshold: {{ .Values.kubecostFrontend.readinessProbe.failureThreshold }} + {{- end }} + {{- if .Values.kubecostFrontend.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: /healthz + port: 9003 + initialDelaySeconds: {{ .Values.kubecostFrontend.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.kubecostFrontend.livenessProbe.periodSeconds }} + failureThreshold: {{ .Values.kubecostFrontend.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.global.containerSecuritycontext }} + securityContext: + {{- toYaml .Values.global.containerSecuritycontext | nindent 12 }} + {{- end }} + {{ end }} + + {{- if and (eq (include "aggregator.deployMethod" .) "singlepod") (not .Values.federatedETL.agentOnly) }} + {{- include "aggregator.containerTemplate" . | nindent 8 }} + {{- if .Values.kubecostAggregator.jaeger.enabled }} + {{- include "aggregator.jaeger.sidecarContainerTemplate" . | nindent 8 }} + {{- end }} + {{- include "aggregator.cloudCost.containerTemplate" . | nindent 8 }} + {{- end }} + + {{- if .Values.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.priority }} + {{- if .Values.priority.enabled }} + {{- if gt (len .Values.priority.name) 0 }} + priorityClassName: {{ .Values.priority.name }} + {{- else }} + priorityClassName: {{ template "cost-analyzer.fullname" . }}-priority + {{- end }} + {{- end }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-frontend-config-map-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-frontend-config-map-template.yaml new file mode 100644 index 000000000..b3f6acd48 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-frontend-config-map-template.yaml @@ -0,0 +1,1328 @@ +{{- if .Values.kubecostFrontend.enabled }} +{{- if and (not .Values.agent) (not .Values.cloudAgent) (not .Values.federatedETL.agentOnly) }} +{{- $serviceName := include "cost-analyzer.serviceName" . -}} +{{- if .Values.saml.enabled }} +{{- if .Values.oidc.enabled }} +{{- fail "SAML and OIDC cannot both be enabled" }} +{{- end }} +{{- end }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-conf + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + nginx.conf: | + gzip_static on; + + # Enable gzip encoding for content of the provided types of 50kb and higher. + gzip on; + gzip_min_length 50000; + gzip_proxied expired no-cache no-store private auth; + gzip_types + application/atom+xml + application/geo+json + application/javascript + application/x-javascript + application/json + application/ld+json + application/manifest+json + application/rdf+xml + application/rss+xml + application/vnd.ms-fontobject + application/wasm + application/x-web-app-manifest+json + application/xhtml+xml + application/xml + font/eot + font/otf + font/ttf + image/bmp + image/svg+xml + text/cache-manifest + text/calendar + text/css + text/javascript + text/markdown + text/plain + text/xml + text/x-component + text/x-cross-domain-policy; + + upstream api { +{{- if .Values.kubecostFrontend.useDefaultFqdn }} + server {{ $serviceName }}.{{ .Release.Namespace }}.svc.cluster.local:9001; +{{- else if (.Values.kubecostFrontend.api).fqdn }} + server {{ .Values.kubecostFrontend.api.fqdn }}; +{{- else }} + server {{ $serviceName }}.{{ .Release.Namespace }}:9001; +{{- end }} + } + + upstream model { +{{- if .Values.kubecostFrontend.useDefaultFqdn }} + server {{ $serviceName }}.{{ .Release.Namespace }}.svc.cluster.local:9003; +{{- else if (.Values.kubecostFrontend.model).fqdn }} + server {{ .Values.kubecostFrontend.model.fqdn }}; +{{- else }} + server {{ $serviceName }}.{{ .Release.Namespace }}:9003; +{{- end }} + } + +{{- if and .Values.clusterController .Values.clusterController.enabled }} + upstream clustercontroller { +{{- if .Values.kubecostFrontend.useDefaultFqdn }} + server {{ template "kubecost.clusterControllerName" . }}-service.{{ .Release.Namespace }}.svc.cluster.local:9731; +{{- else }} +{{- if (.Values.kubecostFrontend.clusterController).fqdn }} + server {{ .Values.kubecostFrontend.clusterController.fqdn }}; +{{- else }} + server {{ template "kubecost.clusterControllerName" . }}-service.{{ .Release.Namespace }}:9731; +{{- end }} +{{- end }} + } +{{- end }} + +{{- if .Values.global.grafana.proxy }} + upstream grafana { +{{- if .Values.global.grafana.enabled }} +{{- if .Values.kubecostFrontend.useDefaultFqdn }} + server {{ .Release.Name }}-grafana.{{ .Release.Namespace }}.svc.cluster.local; +{{- else }} +{{- if .Values.global.grafana.fqdn }} + server {{ .Values.global.grafana.fqdn }}; +{{- else }} + server {{ .Release.Name }}-grafana.{{ .Release.Namespace }}; +{{- end }} +{{- end }} +{{- else }} + server {{.Values.global.grafana.domainName}}; +{{- end }} + } +{{- end }} + + {{- if .Values.forecasting.enabled }} + upstream forecasting { + {{- if .Values.kubecostFrontend.useDefaultFqdn }} + server {{ .Release.Name }}-forecasting.{{ .Release.Namespace }}.svc.cluster.local:5000; + {{- else }} + {{- if (.Values.kubecostFrontend.forcasting).fqdn }} + server {{ .Values.kubecostFrontend.forcasting.fqdn }}; + {{- else }} + server {{ .Release.Name }}-forecasting.{{ .Release.Namespace }}:5000; + {{- end }} + {{- end }} + } + {{- end }} + + {{- if and (not .Values.agent) (not .Values.cloudAgent) (not (eq (include "aggregator.deployMethod" .) "disabled")) }} + upstream aggregator { + {{- if .Values.kubecostFrontend.useDefaultFqdn }} + server {{ .Release.Name }}-aggregator.{{ .Release.Namespace }}.svc.cluster.local:9004; + {{- else }} + {{- if (.Values.kubecostFrontend.aggregator).fqdn }} + server {{ .Values.kubecostFrontend.aggregator.fqdn }}; + {{- else }} + server {{ .Release.Name }}-aggregator.{{ .Release.Namespace }}:9004; + {{- end }} + {{- end }} + } + upstream cloudCost { + {{- if .Values.kubecostFrontend.useDefaultFqdn }} + server {{ template "cloudCost.serviceName" . }}.{{ .Release.Namespace }}.svc.cluster.local:9005; + {{- else }} + {{- if (.Values.kubecostFrontend.cloudCost).fqdn }} + server {{ .Values.kubecostFrontend.cloudCost.fqdn }}; + {{- else }} + server {{ template "cloudCost.serviceName" . }}.{{ .Release.Namespace }}:9005; + {{- end }} + {{- end }} + } + {{- end }} + + {{- if and .Values.diagnostics.enabled .Values.diagnostics.primary.enabled .Values.diagnostics.deployment.enabled }} + {{- if (not (empty .Values.kubecostModel.federatedStorageConfigSecret )) }} + upstream multi-cluster-diagnostics { + {{- if .Values.kubecostFrontend.useDefaultFqdn }} + server {{ template "diagnostics.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:9007; + {{- else}} + {{- if (.Values.kubecostFrontend.multiClusterDiagnostics).fqdn }} + server {{ .Values.kubecostFrontend.multiClusterDiagnostics.fqdn }}; + {{- else }} + server {{ template "diagnostics.fullname" . }}.{{ .Release.Namespace }}:9007; + {{- end }} + {{- end }} + } + {{- end }} + {{- end }} + + server { + server_name _; + root /var/www; + index index.html; + + add_header Cache-Control "must-revalidate"; + + {{- if .Values.kubecostFrontend.extraServerConfig }} + {{- .Values.kubecostFrontend.extraServerConfig | toString | nindent 8 -}} + {{- else }} + large_client_header_buffers 4 32k; + {{- end }} + + error_page 504 /custom_504.html; + location = /custom_504.html { + internal; + } + +{{- if or .Values.saml.enabled .Values.oidc.enabled }} + add_header Cache-Control "max-age=0"; + location / { + auth_request /auth; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + error_page 401 = /login; + try_files $uri $uri/ /index.html; + } + location /healthz { + add_header 'Content-Type' 'text/plain'; + return 200 "healthy\n"; + } +{{- else }} + add_header Cache-Control "max-age=300"; + location / { + try_files $uri $uri/ /index.html; + } +{{- end }} +{{- if .Values.imageVersion }} + add_header ETag "{{ $.Values.imageVersion }}"; +{{- else }} + add_header ETag "{{ $.Chart.Version }}"; +{{- end }} +{{- if .Values.kubecostFrontend.tls }} +{{- if .Values.kubecostFrontend.tls.enabled }} +{{- if .Values.kubecostFrontend.tls.specifyProtocols }} + ssl_protocols {{ $.Values.kubecostFrontend.tls.protocols }}; +{{- end }} + ssl_certificate /etc/ssl/certs/kc.crt; + ssl_certificate_key /etc/ssl/certs/kc.key; + listen {{ .Values.service.targetPort }} ssl; +{{- if .Values.kubecostFrontend.ipv6.enabled }} + listen [::]:{{ .Values.service.targetPort }} ssl; +{{- end }} +{{- else }} + listen {{ .Values.service.targetPort }}; +{{- if .Values.kubecostFrontend.ipv6.enabled }} + listen [::]:{{ .Values.service.targetPort }}; +{{- end }} +{{- end }} +{{- else }} + listen {{ .Values.service.targetPort }}; +{{- if .Values.kubecostFrontend.ipv6.enabled }} + listen [::]:{{ .Values.service.targetPort }}; +{{- end }} +{{- end }} + location /api/ { + {{- if or .Values.saml.enabled .Values.oidc.enabled }} + auth_request /auth; + {{- end }} + proxy_pass http://api/; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location /model/ { + proxy_connect_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_send_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://model/; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + {{- if .Values.kubecostFrontend.extraModelConfigs }} + {{- .Values.kubecostFrontend.extraModelConfigs | toString | nindent 12 -}} + {{- end }} + } + + location ~ ^/(turndown|cluster)/ { + + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS' always; +{{- if .Values.clusterController }} +{{- if .Values.clusterController.enabled }} + {{- if or .Values.saml .Values.oidc }} + {{- if or .Values.saml.enabled .Values.oidc.enabled }} + auth_request /auth; + {{- else if .Values.saml.rbac.enabled}} + auth_request /authrbac; + {{- end }} + {{- end }} + + rewrite ^/(?:turndown|cluster)/(.*)$ /$1 break; + proxy_pass http://clustercontroller; + proxy_connect_timeout 180; + proxy_send_timeout 180; + proxy_read_timeout 180; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + +{{- else }} + return 404; +{{- end }} +{{- else }} + return 404; +{{- end }} + } +{{- if and (or .Values.saml.enabled .Values.oidc.enabled) (not (eq (include "aggregator.deployMethod" .) "disabled")) }} + {{- if .Values.oidc.enabled }} + location /oidc/ { + proxy_connect_timeout 180; + proxy_send_timeout 180; + proxy_read_timeout 180; + proxy_pass http://aggregator/oidc/; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + {{- end }} + {{- if .Values.saml.enabled }} + location /saml/ { + proxy_connect_timeout 180; + proxy_send_timeout 180; + proxy_read_timeout 180; + proxy_pass http://aggregator/saml/; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + {{- end }} + {{- if or .Values.saml.enabled .Values.oidc.enabled}} + location /login { + proxy_connect_timeout 180; + proxy_send_timeout 180; + proxy_read_timeout 180; + proxy_pass http://aggregator/login; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Original-URI $request_uri; + } + + location /logout { + proxy_connect_timeout 180; + proxy_send_timeout 180; + proxy_read_timeout 180; + proxy_pass http://aggregator/logout; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + {{- end }} +{{- end }} + {{- if .Values.global.grafana.proxy }} + location /grafana/ { + {{- if .Values.saml.enabled }} + auth_request /auth; + {{- end }} + proxy_pass http://grafana/; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + } + {{ end }} + {{- if .Values.oidc.enabled }} + location /auth { + proxy_pass http://aggregator/isAuthenticated; + } + {{- end }} + {{- if .Values.saml.enabled }} + location /auth { + proxy_pass http://aggregator/isAuthenticated; + } + {{- if .Values.saml.rbac.enabled }} + location /authrbac { + proxy_pass http://aggregator/isAdminAuthenticated; + } + {{- end }} + {{- end }} + + +{{- if and (not .Values.agent) (not .Values.cloudAgent) (not (eq (include "aggregator.deployMethod" .) "disabled")) }} + # TODO make aggregator route the default, start special-casing + # cost-model APIs + + # Aggregator proxy + {{- if and (.Values.kubecostDeployment) (.Values.kubecostDeployment.queryServiceReplicas) (gt (.Values.kubecostDeployment.queryServiceReplicas | toString | atoi) 0) }} + {{- fail "The Kubecost Aggregator should not be used at the same time as Query Service Replicas" }} + {{- end }} + + location = /model/allocation { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/allocation; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + {{- if not .Values.kubecostFrontend.trendsDisabled }} + location = /model/allocation/trends { + proxy_read_timeout 300; + proxy_pass http://aggregator/allocation/trends; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + {{ end }} + location = /model/allocation/view { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/allocation/view; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/allocation/summary { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/allocation/summary; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/allocation/summary/topline { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/allocation/summary/topline; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/allocation/autocomplete { + proxy_read_timeout 300; + proxy_pass http://aggregator/allocation/autocomplete; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/allocation/carbon { + proxy_read_timeout 300; + proxy_pass http://aggregator/allocation/carbon; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/assets { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/assets; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/assets/topline { + proxy_read_timeout 300; + proxy_pass http://aggregator/assets/topline; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/assets/graph { + proxy_read_timeout 300; + proxy_pass http://aggregator/assets/graph; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/assets/totals { + return 501 "Aggregator does not support this endpoint."; + } + location = /model/assets/diff { + return 501 "Aggregator does not support this endpoint."; + } + location = /model/assets/breakdown { + return 501 "Aggregator does not support this endpoint."; + } + location = /model/assets/autocomplete { + proxy_read_timeout 300; + proxy_pass http://aggregator/assets/autocomplete; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/assets/carbon { + proxy_read_timeout 300; + proxy_pass http://aggregator/assets/carbon; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings/requestSizingV2 { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings/requestSizingV2; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings/requestSizingV2/topline { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings/requestSizingV2/topline; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings/clusterSizingETL { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings/clusterSizingETL; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloudCost { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/cloudCost; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloudCost/view/graph { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/cloudCost/view/graph; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloudCost/view/totals { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/cloudCost/view/totals; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloudCost/view/table { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/cloudCost/view/table; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloudCost/view/trends { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/cloudCost/view/trends; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloudCost/autocomplete { + proxy_read_timeout 300; + proxy_pass http://aggregator/cloudCost/autocomplete; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/clusters/status { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/clusters/status; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings/abandonedWorkloads { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings/abandonedWorkloads; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings/abandonedWorkloads/topline { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings/abandonedWorkloads/topline; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings/unclaimedVolumes { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings/unclaimedVolumes; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings/localLowDisks { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings/localLowDisks; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings/persistentVolumeSizing { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings/persistentVolumeSizing; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings/persistentVolumeSizing/topline { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings/persistentVolumeSizing/topline; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/reports/allocation { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/reports/allocation; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/reports/asset { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/reports/asset; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/reports/advanced { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/reports/advanced; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/reports/cloudCost { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/reports/cloudCost; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/reports/group { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/reports/group; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + # this is a special case to handle /reports/group/:group in the Kubecost Aggregator. prior to aggregator, this endpoint + # was handled by /model/, so no special case proxies were required. without this, /model/reports/groups/?foo=bar + # will be directed to /reports/groups?foo=bar (note the missing /model prefix) + location ~ ^/model/reports/group/ { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/reports/group/$is_args$args; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/budget { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/budget; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/budgets { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/budgets; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collection { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collection; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collections { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collections; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collection/query/total { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collection/query/total; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collection/query/timeseries { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collection/query/timeseries; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collection/query/complement { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collection/query/complement; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collection/query/complement/cloud { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collection/query/complement/cloud; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collection/query/complement/kubernetes { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collection/query/complement/kubernetes; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location = /model/collections/query/total { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collections/query/total; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collections/query/timeseries { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collections/query/timeseries; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collections/query/complement { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collections/query/complement; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collections/query/complement/cloud { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collections/query/complement/cloud; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collections/query/complement/kubernetes { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collections/query/complement/kubernetes; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collection/cache/status { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collection/cache/status; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/networkinsights { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/networkinsights; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/networkinsights/graph { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/networkinsights/graph; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/rbacGroups { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/rbacGroups; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/smtp { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/smtp; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/smtp/test { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/smtp/test; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/teams { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/teams; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/team { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/team; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/users { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/users; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/user { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/user; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/serviceAccounts { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/serviceAccounts; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/serviceAccount { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/serviceAccount; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/debug/orchestrator { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/debug/orchestrator; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/prediction/speccost { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/prediction/speccost; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/coreCount { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/coreCount; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/tableWindowCount { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/tableWindowCount; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/containersPerDay { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/containersPerDay; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/nodesPerDay { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/nodesPerDay; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/containerLabelStats { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/containerLabelStats; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/containerAnnotationStats { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/containerAnnotationStats; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/cloudCostsPerDay { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/cloudCostsPerDay; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/containerWithoutMatchingNode { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/containerWithoutMatchingNode; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/containerDuplicateNoId { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/containerDuplicateNoId; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/containerDuplicateWithId { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/containerDuplicateWithId; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/nodeDuplicateNoId { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/nodeDuplicateNoId; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/debug/ingestionRecords { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/debug/ingestionRecords; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/debug/ingestionSummary { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/debug/ingestionSummary; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/debug/derivationRecords { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/debug/derivationRecords; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/databaseDirectory { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/databaseDirectory; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/getApiConfig { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/getApiConfig; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/setApiConfig { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/setApiConfig; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/enablements { + proxy_read_timeout 300; + proxy_pass http://aggregator/enablements; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/customCost/total { + proxy_read_timeout 300; + proxy_pass http://aggregator/customCost/total; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/customCost/timeseries { + proxy_read_timeout 300; + proxy_pass http://aggregator/customCost/timeseries; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/providerOptimization { + proxy_read_timeout 300; + proxy_pass http://aggregator/providerOptimization; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location = /model/getProductKey { + proxy_read_timeout 300; + proxy_pass http://aggregator/getProductKey; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/setProductKey { + proxy_read_timeout 300; + proxy_pass http://aggregator/setProductKey; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/trialStatus { + proxy_read_timeout 300; + proxy_pass http://aggregator/trialStatus; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/startProductTrial { + proxy_read_timeout 300; + proxy_pass http://aggregator/startProductTrial; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/resetProductTrial { + proxy_read_timeout 300; + proxy_pass http://aggregator/resetProductTrial; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/extendProductTrial { + proxy_read_timeout 300; + proxy_pass http://aggregator/extendProductTrial; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/expireProductTrial { + proxy_read_timeout 300; + proxy_pass http://aggregator/expireProductTrial; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + #Cloud Cost Endpoints + location = /model/cloudCost/status { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://cloudCost/cloudCost/status; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloudCost/rebuild { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://cloudCost/cloudCost/rebuild; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloudCost/repair { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://cloudCost/cloudCost/repair; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloud/config { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://cloudCost/cloud/config; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloud/config/export { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://cloudCost/cloud/config/export; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloud/config/enable { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://cloudCost/cloud/config/enable; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloud/config/disable { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://cloudCost/cloud/config/disable; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloudCost/integration/validate { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://cloudCost/cloudCost/integration/validate; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/customCost/status { + proxy_read_timeout 300; + proxy_pass http://cloudCost/customCost/status; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/customCost/rebuild { + proxy_read_timeout 300; + proxy_pass http://cloudCost/customCost/rebuild; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +{{- end }} + location = /model/hideOrphanedResources { + default_type 'application/json'; + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS' always; + {{- if .Values.kubecostFrontend.hideOrphanedResources }} + return 200 '{"hideOrphanedResources": "true"}'; + {{- else }} + return 200 '{"hideOrphanedResources": "false"}'; + {{- end }} + } + location = /model/hideDiagnostics { + default_type 'application/json'; + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS' always; + {{- if .Values.kubecostFrontend.hideDiagnostics }} + return 200 '{"hideDiagnostics": "true"}'; + {{- else }} + return 200 '{"hideDiagnostics": "false"}'; + {{- end }} + } + + {{- if .Values.kubecostFrontend.trendsDisabled }} + location /model/allocation/trends { + return 204 'endpoint disabled'; + } + {{ end }} + + location /model/multi-cluster-diagnostics-enabled { + default_type 'application/json'; + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS' always; + {{- if and .Values.diagnostics.enabled .Values.diagnostics.primary.enabled }} + {{- if (not (empty .Values.kubecostModel.federatedStorageConfigSecret )) }} + return 200 '{"multiClusterDiagnosticsEnabled": true}'; + {{- end }} + {{- else }} + return 200 '{"multiClusterDiagnosticsEnabled": false}'; + {{- end }} + } + + {{- if and .Values.diagnostics.enabled .Values.diagnostics.primary.enabled .Values.diagnostics.deployment.enabled }} + {{- if (not (empty .Values.kubecostModel.federatedStorageConfigSecret )) }} + + # When the Multi-cluster Diagnostics Service is run within the + # cost-model container, its endpoint is available at the path + # `/model/diagnostics/multicluster`. No additional Nginx path forwarding + # needed. When the Multi-cluster Diagnostics Service is run as a K8s + # Deployment, we should forward that path to the K8s Service. + location /model/diagnostics/multicluster { + default_type 'application/json'; + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS' always; + proxy_read_timeout 300; + proxy_pass http://multi-cluster-diagnostics/status; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + # simple alias for support + location /mcd { + default_type 'application/json'; + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS' always; + proxy_read_timeout 300; + proxy_pass http://multi-cluster-diagnostics/status?window=7d; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + {{- end }} + {{- end }} + + location /model/aggregatorEnabled { + default_type 'application/json'; + return 200 '{"aggregatorEnabled": "true"}'; + } + + {{- if .Values.forecasting.enabled }} + location /forecasting { + default_type 'application/json'; + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS' always; + proxy_read_timeout 300; + proxy_pass http://forecasting/; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + {{- else }} + location /forecasting { + default_type 'application/json'; + return 405 '{"forecastingEnabled": "false"}'; + } + {{- end }} + + location /model/productConfigs { + default_type 'application/json'; + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS' always; + return 200 '\n + { + "ssoConfigured": "{{ template "ssoEnabled" . }}", + "rbacTeamsEnabled": "{{ template "rbacTeamsEnabled" . }}", + "dataBackupConfigured": "{{ template "dataBackupConfigured" . }}", + "costEventsAuditEnabled": "{{ template "costEventsAuditEnabled" . }}", + "frontendDeployMethod": "{{ template "frontend.deployMethod" . }}", + "pluginsEnabled": "{{ template "pluginsEnabled" . }}", + "carbonEstimatesEnabled": "{{ template "carbonEstimatesEnabled" . }}", + "clusterControllerEnabled": "{{ template "clusterControllerEnabled" . }}", + "forecastingEnabled": "{{ template "forecastingEnabled" . }}", + "chartVersion": "2.3.4" + } + '; + } + } + +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-ingress-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-ingress-template.yaml new file mode 100644 index 000000000..4ac0693dd --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-ingress-template.yaml @@ -0,0 +1,56 @@ +{{- if .Values.ingress -}} +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "cost-analyzer.fullname" . -}} +{{- $serviceName := "" -}} +{{- if eq (include "frontend.deployMethod" .) "haMode" }} +{{- $serviceName = include "frontend.serviceName" . }} +{{- else }} +{{- $serviceName = include "cost-analyzer.serviceName" . -}} +{{- end }} +{{- $ingressPaths := .Values.ingress.paths -}} +{{- $ingressPathType := .Values.ingress.pathType -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- with .Values.ingress.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: +{{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} +{{- end }} +{{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ . | quote }} + http: + paths: + {{- range $ingressPaths }} + - path: {{ . }} + pathType: {{ $ingressPathType }} + backend: + service: + name: {{ $serviceName }} + port: + name: tcp-frontend + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-metrics-config-map-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-metrics-config-map-template.yaml new file mode 100644 index 000000000..136d7fa9a --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-metrics-config-map-template.yaml @@ -0,0 +1,13 @@ +{{- if .Values.kubecostProductConfigs -}} +{{- if .Values.kubecostProductConfigs.metricsConfigs -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ default "metrics-config" .Values.metricsConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + metrics.json: '{{ toJson .Values.kubecostProductConfigs.metricsConfigs }}' +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-costs-config-map-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-costs-config-map-template.yaml new file mode 100644 index 000000000..378fca584 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-costs-config-map-template.yaml @@ -0,0 +1,16 @@ +{{- if .Values.networkCosts -}} +{{- if .Values.networkCosts.enabled -}} +{{- if .Values.networkCosts.config -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: network-costs-config + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + config.yaml: | +{{- toYaml .Values.networkCosts.config | nindent 4 }} +{{- end -}} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-costs-podmonitor-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-costs-podmonitor-template.yaml new file mode 100644 index 000000000..d0b5b5dd8 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-costs-podmonitor-template.yaml @@ -0,0 +1,32 @@ +{{- if .Values.networkCosts }} +{{- if .Values.networkCosts.enabled }} +{{- if .Values.networkCosts.podMonitor }} +{{- if .Values.networkCosts.podMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: PodMonitor +metadata: + name: {{ include "cost-analyzer.networkCostsName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if .Values.networkCosts.podMonitor.additionalLabels }} + {{ toYaml .Values.networkCosts.podMonitor.additionalLabels | nindent 4 }} + {{- end }} +spec: + podMetricsEndpoints: + - port: http-server + honorLabels: true + interval: 1m + scrapeTimeout: 10s + path: /metrics + scheme: http + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + app: {{ template "cost-analyzer.networkCostsName" . }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-costs-service-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-costs-service-template.yaml new file mode 100644 index 000000000..0ac70718d --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-costs-service-template.yaml @@ -0,0 +1,34 @@ +{{- if .Values.networkCosts }} +{{- if .Values.networkCosts.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "cost-analyzer.networkCostsName" . }} + namespace: {{ .Release.Namespace }} +{{- if (or .Values.networkCosts.service.annotations .Values.networkCosts.prometheusScrape) }} + annotations: +{{- if .Values.networkCosts.service.annotations }} +{{ toYaml .Values.networkCosts.service.annotations | indent 4 }} +{{- end }} +{{- if .Values.networkCosts.prometheusScrape }} + prometheus.io/scrape: "true" + prometheus.io/port: {{ (quote .Values.networkCosts.port) | default (quote 3001) }} +{{- end }} +{{- end }} + labels: + {{- include "networkcosts.commonLabels" . | nindent 4 }} + {{- if .Values.networkCosts.service.labels }} + {{ toYaml .Values.networkCosts.service.labels | nindent 4 }} + {{- end }} +spec: + clusterIP: None + ports: + - name: metrics + port: {{ .Values.networkCosts.port | default 3001 }} + protocol: TCP + targetPort: {{ .Values.networkCosts.port | default 3001 }} + selector: + {{- include "networkcosts.selectorLabels" . | nindent 4 }} + type: ClusterIP +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-costs-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-costs-template.yaml new file mode 100644 index 000000000..7af788153 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-costs-template.yaml @@ -0,0 +1,149 @@ +{{- if .Values.networkCosts -}} +{{- if .Values.networkCosts.enabled -}} +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: {{ template "cost-analyzer.networkCostsName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "networkcosts.commonLabels" . | nindent 4 }} + {{- if .Values.networkCosts.additionalLabels }} + {{- toYaml .Values.networkCosts.additionalLabels | nindent 4 }} + {{- end }} +spec: + {{- if .Values.networkCosts.updateStrategy }} + updateStrategy: + {{- toYaml .Values.networkCosts.updateStrategy | nindent 4 }} + {{- end }} + selector: + matchLabels: + {{- include "networkcosts.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.networkCosts.annotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "networkcosts.commonLabels" . | nindent 8 }} + {{- if .Values.networkCosts.additionalLabels }} + {{- toYaml .Values.networkCosts.additionalLabels | nindent 8 }} + {{- end }} + spec: + {{- if .Values.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.imagePullSecrets | indent 2 }} + {{- end }} + hostNetwork: true + serviceAccountName: {{ template "cost-analyzer.serviceAccountName" . }} + containers: + - name: {{ template "cost-analyzer.networkCostsName" . }} + {{- if eq (typeOf .Values.networkCosts.image) "string" }} + image: {{ .Values.networkCosts.image }} + {{- else }} + image: {{ .Values.networkCosts.image.repository }}:{{ .Values.networkCosts.image.tag }} + {{- end}} + {{- if .Values.networkCosts.extraArgs }} + args: + {{- toYaml .Values.networkCosts.extraArgs | nindent 8 }} + {{- end }} +{{- if .Values.networkCosts.imagePullPolicy }} + imagePullPolicy: {{ .Values.networkCosts.imagePullPolicy }} +{{- else }} + imagePullPolicy: Always +{{- end }} +{{- if .Values.networkCosts.resources }} + resources: {{- toYaml .Values.networkCosts.resources | nindent 10 }} +{{- end }} + env: + {{- if .Values.networkCosts.hostProc }} + - name: HOST_PROC + value: {{ .Values.networkCosts.hostProc.mountPath }} + {{- end }} + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: HOST_PORT + value: {{ (quote .Values.networkCosts.port) | default (quote 3001) }} + - name: TRAFFIC_LOGGING_ENABLED + value: {{ (quote .Values.networkCosts.trafficLogging) | default (quote true) }} + - name: LOG_LEVEL + value: {{ .Values.networkCosts.logLevel | default "info" }} + {{- if .Values.networkCosts.softMemoryLimit }} + - name: GOMEMLIMIT + value: {{ .Values.networkCosts.softMemoryLimit }} + {{- end }} + {{- if .Values.networkCosts.heapMonitor }} + {{- if .Values.networkCosts.heapMonitor.enabled }} + - name: HEAP_MONITOR_ENABLED + value: "true" + - name: HEAP_MONITOR_THRESHOLD + value: {{ .Values.networkCosts.heapMonitor.threshold }} + {{- if .Values.networkCosts.heapMonitor.outFile }} + - name: HEAP_MONITOR_OUTPUT + value: {{ .Values.networkCosts.heapMonitor.outFile }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.networkCosts.healthCheckProbes }} + {{- toYaml .Values.networkCosts.healthCheckProbes | nindent 8 }} + {{- end }} + volumeMounts: + {{- if .Values.networkCosts.hostProc }} + - mountPath: {{ .Values.networkCosts.hostProc.mountPath }} + name: host-proc + {{- else }} + - mountPath: /net + name: nf-conntrack + - mountPath: /netfilter + name: netfilter + {{- end }} + {{- if .Values.networkCosts.config }} + - mountPath: /network-costs/config + name: network-costs-config + {{- end }} + securityContext: + privileged: true + {{- if .Values.networkCosts.additionalSecurityContext }} + {{- toYaml .Values.networkCosts.additionalSecurityContext | nindent 10 }} + {{- end }} + ports: + - name: http-server + containerPort: {{ .Values.networkCosts.port | default 3001 }} + hostPort: {{ .Values.networkCosts.port | default 3001 }} +{{- if .Values.networkCosts.priorityClassName }} + priorityClassName: "{{ .Values.networkCosts.priorityClassName }}" +{{- end }} + {{- with .Values.networkCosts.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 10 }} + {{- end }} +{{- if .Values.networkCosts.tolerations }} + tolerations: +{{ toYaml .Values.networkCosts.tolerations | indent 8 }} + {{- end }} + {{- with .Values.networkCosts.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + {{- if .Values.networkCosts.config }} + - name: network-costs-config + configMap: + name: network-costs-config + {{- end }} + {{- if .Values.networkCosts.hostProc }} + - name: host-proc + hostPath: + path: {{ default "/proc" .Values.networkCosts.hostProc.hostPath }} + {{- else }} + - name: nf-conntrack + hostPath: + path: /proc/net + - name: netfilter + hostPath: + path: /proc/sys/net/netfilter + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-policy-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-policy-template.yaml new file mode 100644 index 000000000..812956f41 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-policy-template.yaml @@ -0,0 +1,47 @@ +{{- if .Values.networkPolicy -}} +{{- if .Values.networkPolicy.costAnalyzer.enabled -}} +kind: NetworkPolicy +apiVersion: networking.k8s.io/v1 +metadata: + name: {{ template "cost-analyzer.fullname" . }} +{{- if .Values.networkPolicy.costAnalyzer.annotations }} + annotations: +{{ toYaml .Values.networkPolicy.costAnalyzer.annotations | indent 4}} +{{- end }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +{{- if .Values.networkPolicy.costAnalyzer.additionalLabels }} +{{ toYaml .Values.networkPolicy.costAnalyzer.additionalLabels | indent 4 }} +{{- end }} +spec: + podSelector: + matchLabels: + {{- include "cost-analyzer.selectorLabels" . | nindent 6 }} + policyTypes: +{{- if .Values.networkPolicy.costAnalyzer.ingressRules }} + - Ingress +{{- end }} +{{- if .Values.networkPolicy.costAnalyzer.egressRules }} + - Egress +{{- end }} +{{- if .Values.networkPolicy.costAnalyzer.egressRules }} + egress: +{{- range $rule := .Values.networkPolicy.costAnalyzer.egressRules }} + - to: +{{ toYaml $rule.selectors | indent 7 }} + ports: +{{ toYaml $rule.ports | indent 9 }} +{{- end }} +{{- end }} +{{- if .Values.networkPolicy.costAnalyzer.ingressRules }} + ingress: +{{- range $rule := .Values.networkPolicy.costAnalyzer.ingressRules }} + - from: +{{ toYaml $rule.selectors | indent 7 }} + ports: +{{ toYaml $rule.ports | indent 9 }} +{{- end }} +{{- end }} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-policy.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-policy.yaml new file mode 100644 index 000000000..77a062e9f --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-network-policy.yaml @@ -0,0 +1,48 @@ +{{- if .Values.networkPolicy -}} +{{- if .Values.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +{{- if .Values.networkPolicy.denyEgress }} +metadata: + name: deny-egress + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "cost-analyzer.selectorLabels" . | nindent 6 }} + policyTypes: + - Egress +{{- else }} +{{- if .Values.networkPolicy.sameNamespace}} +metadata: + name: shared-namespace + namespace: {{ default "kubecost" .Values.networkPolicy.namespace}} +spec: + podSelector: + matchLabels: + app: prometheus + component: server +{{- else }} +metadata: + name: closed-traffic + namespace: {{ default "kubecost" .Values.networkPolicy.namespace}} +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: cost-analyzer +{{- end }} + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/name: cost-analyzer + - namespaceSelector: + matchLabels: + name: k8s-kubecost +{{- end }} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-networks-costs-ocp-scc.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-networks-costs-ocp-scc.yaml new file mode 100644 index 000000000..8602cb0c6 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-networks-costs-ocp-scc.yaml @@ -0,0 +1,30 @@ +{{- if and (.Capabilities.APIVersions.Has "security.openshift.io/v1/SecurityContextConstraints") (.Values.global.platforms.openshift.scc.networkCosts) (.Values.networkCosts.enabled) }} +apiVersion: security.openshift.io/v1 +kind: SecurityContextConstraints +metadata: + name: {{ template "cost-analyzer.networkCostsName" . }} +priority: 10 +allowPrivilegedContainer: true +allowHostDirVolumePlugin: true +allowHostNetwork: true +allowHostPorts: true +allowHostPID: false +allowHostIPC: false +readOnlyRootFilesystem: false +runAsUser: + type: RunAsAny +fsGroup: + type: RunAsAny +seLinuxContext: + type: RunAsAny +supplementalGroups: + type: RunAsAny +seccompProfiles: +- runtime/default +volumes: + - hostPath + - projected + - configMap +users: + - system:serviceaccount:{{ .Release.Namespace }}:{{ template "cost-analyzer.serviceAccountName" . }} +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-ocp-route.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-ocp-route.yaml new file mode 100644 index 000000000..3438dcd54 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-ocp-route.yaml @@ -0,0 +1,25 @@ +{{- if and (.Capabilities.APIVersions.Has "route.openshift.io/v1/Route") (.Values.global.platforms.openshift.enabled) (.Values.global.platforms.openshift.route.enabled) }} +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: {{ template "cost-analyzer.fullname" . }}-route + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- with .Values.global.platforms.openshift.route.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.global.platforms.openshift.route.host }} + host: "{{ .Values.global.platforms.openshift.route.host }}" + {{- end }} + port: + targetPort: tcp-frontend + tls: + termination: edge + to: + kind: Service + name: {{ template "cost-analyzer.serviceName" . }} + weight: 100 + wildcardPolicy: None +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-oidc-config-map-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-oidc-config-map-template.yaml new file mode 100644 index 000000000..7a2c2e69f --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-oidc-config-map-template.yaml @@ -0,0 +1,48 @@ +{{- if .Values.oidc }} +{{- if .Values.oidc.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "cost-analyzer.fullname" . }}-oidc + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: +{{- $root := . }} + oidc.json: |- + { + "enabled" : {{ .Values.oidc.enabled }}, + "useIDToken" : {{ .Values.oidc.useIDToken | default "false" }}, + "clientID" : "{{ .Values.oidc.clientID }}", + {{- if .Values.oidc.existingCustomSecret.enabled }} + "secretName" : "{{ .Values.oidc.existingCustomSecret.name }}", + {{- else }} + "secretName" : "{{ .Values.oidc.secretName }}", + {{- end }} + "authURL" : "{{ .Values.oidc.authURL }}", + "loginRedirectURL" : "{{ .Values.oidc.loginRedirectURL }}", + "discoveryURL" : "{{ .Values.oidc.discoveryURL }}", + "hostedDomain" : "{{ .Values.oidc.hostedDomain }}", + "skipOnlineTokenValidation" : "{{ .Values.oidc.skipOnlineTokenValidation | default "false" }}", + "rbac" : { + "enabled" : {{ .Values.oidc.rbac.enabled }}, + "groups" : [ + {{- range $i, $g := .Values.oidc.rbac.groups }} + {{- if ne $i 0 }},{{- end }} + { + "roleName": "{{ $g.name }}", + "enabled": {{ $g.enabled }}, + "claimName": "{{ $g.claimName }}", + "claimValues": [ + {{- range $j, $v := $g.claimValues }} + {{- if ne $j 0 }},{{- end }} + "{{ $v }}" + {{- end }} + ] + } + {{- end }} + ] + } + } +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-pkey-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-pkey-configmap.yaml new file mode 100644 index 000000000..6420ac75a --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-pkey-configmap.yaml @@ -0,0 +1,23 @@ +{{- if .Values.kubecostProductConfigs }} +{{- if .Values.kubecostProductConfigs.productKey }} +{{- if .Values.kubecostProductConfigs.productKey.enabled }} +# If the productKey.key is not specified, the configmap will not be created +{{- if .Values.kubecostProductConfigs.productKey.key }} +# If the secretname is specified, the configmap will not be created +{{- if not .Values.kubecostProductConfigs.productKey.secretname }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ default "product-configs" .Values.productConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + {{- if .Values.kubecostProductConfigs.productKey.key }} + key: {{ .Values.kubecostProductConfigs.productKey.key | quote }} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-pricing-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-pricing-configmap.yaml new file mode 100644 index 000000000..1325d4434 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-pricing-configmap.yaml @@ -0,0 +1,141 @@ +{{- if .Values.kubecostProductConfigs }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ default "pricing-configs" .Values.pricingConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + {{- if .Values.kubecostProductConfigs.defaultModelPricing }} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.enabled }} + {{- if .Values.kubecostProductConfigs.customPricesEnabled }} + customPricesEnabled: "{{ .Values.kubecostProductConfigs.customPricesEnabled }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.CPU }} + CPU: "{{ .Values.kubecostProductConfigs.defaultModelPricing.CPU | toString }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.spotCPU }} + spotCPU: "{{ .Values.kubecostProductConfigs.defaultModelPricing.spotCPU | toString }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.RAM }} + RAM: "{{ .Values.kubecostProductConfigs.defaultModelPricing.RAM | toString }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.spotRAM }} + spotRAM: "{{ .Values.kubecostProductConfigs.defaultModelPricing.spotRAM | toString }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.GPU }} + GPU: "{{ .Values.kubecostProductConfigs.defaultModelPricing.GPU | toString }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.spotGPU }} + spotGPU: "{{ .Values.kubecostProductConfigs.defaultModelPricing.spotGPU | toString }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.storage }} + storage: "{{ .Values.kubecostProductConfigs.defaultModelPricing.storage | toString }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.zoneNetworkEgress }} + zoneNetworkEgress: "{{ .Values.kubecostProductConfigs.defaultModelPricing.zoneNetworkEgress | toString }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.regionNetworkEgress }} + regionNetworkEgress: "{{ .Values.kubecostProductConfigs.defaultModelPricing.regionNetworkEgress | toString }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.internetNetworkEgress }} + internetNetworkEgress: "{{ .Values.kubecostProductConfigs.defaultModelPricing.internetNetworkEgress | toString }}" + {{- end -}} + {{- end -}} + {{- end -}} + {{- if .Values.kubecostProductConfigs.clusterName }} + clusterName: "{{ .Values.kubecostProductConfigs.clusterName }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.clusterAccountID }} + clusterAccountID: "{{ .Values.kubecostProductConfigs.clusterAccountID }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.currencyCode }} + currencyCode: "{{ .Values.kubecostProductConfigs.currencyCode }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.azureBillingRegion }} + azureBillingRegion: "{{ .Values.kubecostProductConfigs.azureBillingRegion }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.azureSubscriptionID }} + azureSubscriptionID: "{{ .Values.kubecostProductConfigs.azureSubscriptionID }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.azureClientID }} + azureClientID: "{{ .Values.kubecostProductConfigs.azureClientID }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.azureTenantID }} + azureTenantID: "{{ .Values.kubecostProductConfigs.azureTenantID }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.azureOfferDurableID }} + azureOfferDurableID: "{{ .Values.kubecostProductConfigs.azureOfferDurableID }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.discount }} + discount: "{{ .Values.kubecostProductConfigs.discount }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.negotiatedDiscount }} + negotiatedDiscount: "{{ .Values.kubecostProductConfigs.negotiatedDiscount }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultIdle }} + defaultIdle: "{{ .Values.kubecostProductConfigs.defaultIdle }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.sharedNamespaces }} + sharedNamespaces: "{{ .Values.kubecostProductConfigs.sharedNamespaces }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.sharedOverhead }} + sharedOverhead: "{{ .Values.kubecostProductConfigs.sharedOverhead }}" + {{- end -}} + {{- if gt (len (toString .Values.kubecostProductConfigs.shareTenancyCosts)) 0 }} + {{- if eq (toString .Values.kubecostProductConfigs.shareTenancyCosts) "false" }} + shareTenancyCosts: "false" + {{- else if eq (toString .Values.kubecostProductConfigs.shareTenancyCosts) "true" }} + shareTenancyCosts: "true" + {{- end -}} + {{- end -}} + {{- if .Values.kubecostProductConfigs.spotLabel }} + spotLabel: "{{ .Values.kubecostProductConfigs.spotLabel }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.spotLabelValue }} + spotLabelValue: "{{ .Values.kubecostProductConfigs.spotLabelValue }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.awsSpotDataRegion }} + spotDataRegion: "{{ .Values.kubecostProductConfigs.awsSpotDataRegion }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.awsSpotDataBucket }} + spotDataBucket: "{{ .Values.kubecostProductConfigs.awsSpotDataBucket }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.awsSpotDataPrefix }} + spotDataPrefix: "{{ .Values.kubecostProductConfigs.awsSpotDataPrefix }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.projectID }} + projectID: "{{ .Values.kubecostProductConfigs.projectID }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.bigQueryBillingDataDataset }} + billingDataDataset: "{{ .Values.kubecostProductConfigs.bigQueryBillingDataDataset }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.athenaProjectID }} + athenaProjectID: "{{ .Values.kubecostProductConfigs.athenaProjectID }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.athenaBucketName }} + athenaBucketName: "{{ .Values.kubecostProductConfigs.athenaBucketName }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.athenaRegion }} + athenaRegion: "{{ .Values.kubecostProductConfigs.athenaRegion }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.athenaDatabase }} + athenaDatabase: "{{ .Values.kubecostProductConfigs.athenaDatabase }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.athenaTable }} + athenaTable: "{{ .Values.kubecostProductConfigs.athenaTable }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.athenaWorkgroup }} + athenaWorkgroup: "{{ .Values.kubecostProductConfigs.athenaWorkgroup }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.masterPayerARN}} + masterPayerARN: "{{ .Values.kubecostProductConfigs.masterPayerARN }}" + {{- end }} + {{- if .Values.kubecostProductConfigs.gpuLabel }} + gpuLabel: "{{ .Values.kubecostProductConfigs.gpuLabel }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.gpuLabelValue }} + gpuLabelValue: "{{ .Values.kubecostProductConfigs.gpuLabelValue }}" + {{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-prometheusrule-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-prometheusrule-template.yaml new file mode 100644 index 000000000..eba7797f3 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-prometheusrule-template.yaml @@ -0,0 +1,22 @@ +{{- if .Values.prometheus }} +{{- if .Values.prometheus.serverFiles }} +{{- if .Values.prometheus.serverFiles.rules }} +{{- if .Values.prometheusRule }} +{{- if .Values.prometheusRule.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ include "cost-analyzer.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if .Values.prometheusRule.additionalLabels }} + {{ toYaml .Values.prometheusRule.additionalLabels | nindent 4 }} + {{- end }} +spec: + {{ toYaml .Values.prometheus.serverFiles.rules | nindent 2 }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-pvc-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-pvc-template.yaml new file mode 100644 index 000000000..82a9cdcd0 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-pvc-template.yaml @@ -0,0 +1,33 @@ +{{- if .Values.persistentVolume -}} +{{- if not .Values.persistentVolume.existingClaim -}} +{{- if .Values.persistentVolume.enabled -}} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "cost-analyzer.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- with .Values.persistentVolume.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.persistentVolume.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + accessModes: + - ReadWriteOnce + {{- if .Values.persistentVolume.storageClass }} + storageClassName: {{ .Values.persistentVolume.storageClass }} + {{ end }} + resources: + requests: + {{- if .Values.persistentVolume }} + storage: {{ .Values.persistentVolume.size }} + {{- else }} + storage: 32.0Gi + {{ end }} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-saml-config-map-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-saml-config-map-template.yaml new file mode 100644 index 000000000..3293f2598 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-saml-config-map-template.yaml @@ -0,0 +1,14 @@ +{{- if .Values.saml }} +{{- if .Values.saml.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "cost-analyzer.fullname" . }}-saml + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: +{{- $root := . }} + saml.json: '{{ toJson .Values.saml }}' +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-saved-reports-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-saved-reports-configmap.yaml new file mode 100644 index 000000000..285229ab2 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-saved-reports-configmap.yaml @@ -0,0 +1,13 @@ +{{- if .Values.global.savedReports }} +{{- if .Values.global.savedReports.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{default "saved-report-configs" .Values.savedReportConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + saved-reports.json: '{{ toJson .Values.global.savedReports.reports }}' +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-server-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-server-configmap.yaml new file mode 100644 index 000000000..57038b9cd --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-server-configmap.yaml @@ -0,0 +1,72 @@ +{{- if .Values.kubecostProductConfigs }} +{{- if or .Values.kubecostProductConfigs.grafanaURL .Values.kubecostProductConfigs.labelMappingConfigs .Values.kubecostProductConfigs.cloudAccountMapping}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ default "app-configs" .Values.appConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: +{{- if .Values.kubecostProductConfigs.labelMappingConfigs }} +{{- if .Values.kubecostProductConfigs.labelMappingConfigs.enabled }} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.owner_label }} + owner_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.owner_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.team_label }} + team_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.team_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.department_label }} + department_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.department_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.product_label }} + product_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.product_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.environment_label }} + environment_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.environment_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.namespace_external_label }} + namespace_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.namespace_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.cluster_external_label }} + cluster_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.cluster_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.controller_external_label }} + controller_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.controller_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.product_external_label }} + product_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.product_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.service_external_label }} + service_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.service_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.deployment_external_label }} + deployment_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.deployment_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.team_external_label }} + team_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.team_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.environment_external_label }} + environment_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.environment_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.department_external_label }} + department_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.department_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.statefulset_external_label }} + statefulset_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.statefulset_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.daemonset_external_label }} + daemonset_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.daemonset_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.pod_external_label }} + pod_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.pod_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.owner_external_label }} + owner_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.owner_external_label }}" + {{- end -}} +{{- end -}} +{{- end -}} + {{- if .Values.kubecostProductConfigs.grafanaURL }} + grafanaURL: "{{ .Values.kubecostProductConfigs.grafanaURL }}" + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-service-account-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-service-account-template.yaml new file mode 100644 index 000000000..f2a2cec80 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-service-account-template.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "cost-analyzer.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +{{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-service-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-service-template.yaml new file mode 100644 index 000000000..82d957fca --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-service-template.yaml @@ -0,0 +1,66 @@ +{{- if and (not .Values.agent) (not .Values.cloudAgent) }} +kind: Service +apiVersion: v1 +metadata: + name: {{ template "cost-analyzer.serviceName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +{{- if .Values.service.labels }} +{{ toYaml .Values.service.labels | indent 4 }} +{{- end }} +{{- if .Values.service.annotations }} + annotations: +{{ toYaml .Values.service.annotations | indent 4 }} +{{- end }} +spec: + selector: + {{- include "cost-analyzer.selectorLabels" . | nindent 4 }} +{{- if .Values.service -}} +{{- if .Values.service.type }} + type: "{{ .Values.service.type }}" +{{- else }} + type: ClusterIP +{{- end }} +{{- else }} + type: ClusterIP +{{- end }} +{{- if (eq .Values.service.type "LoadBalancer") }} + {{- if .Values.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: +{{ toYaml .Values.service.loadBalancerSourceRanges | indent 4 }} + {{- end -}} +{{- end }} + ports: + - name: tcp-model + port: 9003 + targetPort: 9003 + {{- with .Values.kubecostModel.extraPorts }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if and (.Values.kubecostFrontend.enabled) (not (eq (include "frontend.deployMethod" .) "haMode")) }} + - name: tcp-frontend + {{- if (eq .Values.service.type "NodePort") }} + {{- if .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + {{- end }} + port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} + {{- end }} + {{- if or .Values.saml.enabled .Values.oidc.enabled}} + - name: apiserver + port: 9007 + targetPort: 9007 + {{- end }} +{{- if .Values.service.sessionAffinity.enabled }} + sessionAffinity: ClientIP + {{- if .Values.service.sessionAffinity.timeoutSeconds }} + sessionAffinityConfig: + clientIP: + timeoutSeconds: {{ .Values.service.sessionAffinity.timeoutSeconds }} + {{- end }} +{{- else }} + sessionAffinity: None +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-servicemonitor-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-servicemonitor-template.yaml new file mode 100644 index 000000000..fb3379246 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-servicemonitor-template.yaml @@ -0,0 +1,34 @@ +{{- if .Values.serviceMonitor }} +{{- if .Values.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "cost-analyzer.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if .Values.serviceMonitor.additionalLabels }} + {{ toYaml .Values.serviceMonitor.additionalLabels | nindent 4 }} + {{- end }} +spec: + endpoints: + - port: tcp-model + honorLabels: true + interval: {{ .Values.serviceMonitor.interval }} + scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout }} + path: /metrics + scheme: http + {{- with .Values.serviceMonitor.metricRelabelings }} + metricRelabelings: {{ toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.serviceMonitor.relabelings }} + relabelings: {{ toYaml . | nindent 8 }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + {{ include "cost-analyzer.selectorLabels" . | nindent 6 }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-smtp-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-smtp-configmap.yaml new file mode 100644 index 000000000..fd00091ce --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/cost-analyzer-smtp-configmap.yaml @@ -0,0 +1,12 @@ + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ default "smtp-configs" .Values.smtpConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +{{- if (((.Values.kubecostProductConfigs).smtp).config) }} +data: + config: {{ .Values.kubecostProductConfigs.smtp.config | quote }} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/diagnostics-deployment.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/diagnostics-deployment.yaml new file mode 100644 index 000000000..1234fa7bb --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/diagnostics-deployment.yaml @@ -0,0 +1,177 @@ +{{- if and .Values.diagnostics.enabled .Values.diagnostics.deployment.enabled }} +{{- if (not (empty .Values.kubecostModel.federatedStorageConfigSecret )) -}} + +{{- if eq .Values.prometheus.server.global.external_labels.cluster_id "cluster-one" }} +{{- fail "Error: The 'cluster_id' is set to default 'cluster-one'. Please update so that the diagnostics service can uniquely identify data coming from this cluster." }} +{{- end }} + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "diagnostics.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "diagnostics.selectorLabels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if .Values.diagnostics.deployment.labels }} + {{- toYaml .Values.diagnostics.deployment.labels | nindent 4 }} + {{- end }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "diagnostics.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "diagnostics.selectorLabels" . | nindent 8 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + annotations: + # Generates a unique annotation upon each `helm upgrade`, forcing a redeployment + {{- if not .Values.global.platforms.cicd.enabled }} + helm-rollout-restarter: {{ randAlphaNum 5 | quote }} + {{- end }} + {{- with .Values.global.podAnnotations}} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + restartPolicy: Always + {{- if .Values.diagnostics.deployment.securityContext }} + securityContext: + {{- toYaml .Values.diagnostics.deployment.securityContext | nindent 8 }} + {{- else if .Values.global.securityContext }} + securityContext: + {{- toYaml .Values.global.securityContext | nindent 8 }} + {{- end }} + serviceAccountName: {{ template "cost-analyzer.serviceAccountName" . }} + volumes: + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: federated-storage-config + secret: + defaultMode: 420 + secretName: {{ .Values.kubecostModel.federatedStorageConfigSecret }} + {{- end }} + - name: config-db + {{- /* #TODO: make pv? */}} + emptyDir: {} + containers: + - name: diagnostics + args: ["diagnostics"] + {{- if .Values.kubecostModel }} + {{- if .Values.kubecostModel.fullImageName }} + image: {{ .Values.kubecostModel.fullImageName }} + {{- else if .Values.imageVersion }} + image: {{ .Values.kubecostModel.image }}:{{ .Values.imageVersion }} + {{- else if eq "development" .Chart.AppVersion }} + image: gcr.io/kubecost1/cost-model-nightly:latest + {{- else }} + image: {{ .Values.kubecostModel.image }}:prod-{{ $.Chart.AppVersion }} + {{- end }} + {{- else }} + image: gcr.io/kubecost1/cost-model:prod-{{ $.Chart.AppVersion }} + {{- end }} + {{- if .Values.kubecostModel.imagePullPolicy }} + imagePullPolicy: {{ .Values.kubecostModel.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + {{- if .Values.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.diagnostics.deployment.containerSecurityContext }} + securityContext: + {{- toYaml .Values.diagnostics.deployment.containerSecurityContext | nindent 12 }} + {{- else if .Values.global.containerSecurityContext }} + securityContext: + {{- toYaml .Values.global.containerSecurityContext | nindent 12 }} + {{- end }} + volumeMounts: + - name: config-db + mountPath: /var/configs/db + readOnly: false + - name: federated-storage-config + mountPath: /var/configs/etl + readOnly: true + env: + {{- if and (.Values.prometheus.server.global.external_labels.cluster_id) (not .Values.prometheus.server.clusterIDConfigmap) }} + - name: CLUSTER_ID + value: {{ .Values.prometheus.server.global.external_labels.cluster_id }} + {{- end }} + {{- if .Values.prometheus.server.clusterIDConfigmap }} + - name: CLUSTER_ID + valueFrom: + configMapKeyRef: + name: {{ .Values.prometheus.server.clusterIDConfigmap }} + key: CLUSTER_ID + {{- end }} + - name: FEDERATED_STORE_CONFIG + value: /var/configs/etl/federated-store.yaml + - name: DIAGNOSTICS_RUN_IN_COST_MODEL + value: "false" + - name: DIAGNOSTICS_KUBECOST_FQDN + value: {{ template "cost-analyzer.serviceName" . }} + - name: DIAGNOSTICS_KUBECOST_NAMESPACE + value: {{ .Release.Namespace }} + - name: DIAGNOSTICS_PRIMARY + value: {{ quote .Values.diagnostics.primary.enabled }} + - name: DIAGNOSTICS_RETENTION + value: {{ .Values.diagnostics.primary.retention }} + - name: DIAGNOSTICS_PRIMARY_READONLY + value: {{ quote .Values.diagnostics.primary.readonly }} + - name: DIAGNOSTICS_POLLING_INTERVAL + value: {{ .Values.diagnostics.pollingInterval }} + - name: DIAGNOSTICS_KEEP_HISTORY + value: {{ quote .Values.diagnostics.keepDiagnosticHistory }} + - name: DIAGNOSTICS_COLLECT_HELM_VALUES + value: {{ quote .Values.diagnostics.collectHelmValues }} + {{- if .Values.systemProxy.enabled }} + - name: HTTP_PROXY + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: http_proxy + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: HTTPS_PROXY + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: https_proxy + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: NO_PROXY + value: {{ .Values.systemProxy.noProxy }} + - name: no_proxy + value: {{ .Values.systemProxy.noProxy }} + {{- end }} + {{- range $key, $value := .Values.diagnostics.deployment.env }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + {{- /* TODO: heatlhcheck that validates the diagnotics pod is healthy */}} + {{- if .Values.diagnostics.primary.enabled}} + readinessProbe: + httpGet: + path: /healthz + port: 9007 + ports: + - name: diagnostics-api + containerPort: 9007 + protocol: TCP + {{- end }} + resources: + {{- toYaml .Values.diagnostics.deployment.resources | nindent 12 }} + {{- with .Values.diagnostics.deployment.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.diagnostics.deployment.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.diagnostics.deployment.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/diagnostics-service.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/diagnostics-service.yaml new file mode 100644 index 000000000..5c0fdebe8 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/diagnostics-service.yaml @@ -0,0 +1,20 @@ +{{- if and .Values.diagnostics.enabled .Values.diagnostics.deployment.enabled .Values.diagnostics.primary.enabled }} +{{- if (not (empty .Values.kubecostModel.federatedStorageConfigSecret )) -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "diagnostics.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "diagnostics.selectorLabels" . | nindent 4 }} +spec: + ports: + - name: diagnostics-api + protocol: TCP + port: 9007 + targetPort: diagnostics-api + selector: + {{- include "diagnostics.selectorLabels" . | nindent 4 }} + type: ClusterIP +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/etl-utils-deployment.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/etl-utils-deployment.yaml new file mode 100644 index 000000000..78aeb8ed3 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/etl-utils-deployment.yaml @@ -0,0 +1,123 @@ +{{- if .Values.etlUtils.enabled }} + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "etlUtils.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "etlUtils.commonLabels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.global.podAnnotations}} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "etlUtils.selectorLabels" . | nindent 6 }} + strategy: + type: Recreate + template: + metadata: + labels: + app.kubernetes.io/name: {{ template "etlUtils.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app: {{ template "etlUtils.name" . }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + restartPolicy: Always + volumes: + {{- if .Values.etlUtils.thanosSourceBucketSecret }} + - name: etl-bucket-config + secret: + defaultMode: 420 + secretName: {{ .Values.etlUtils.thanosSourceBucketSecret }} + {{- end }} + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: federated-storage-config + secret: + defaultMode: 420 + secretName: {{ .Values.kubecostModel.federatedStorageConfigSecret }} + {{- end }} + serviceAccountName: {{ template "cost-analyzer.serviceAccountName" . }} + containers: + - name: {{ template "etlUtils.name" . }} + {{- if .Values.kubecostModel }} + {{- if .Values.kubecostModel.openSourceOnly }} + {{- fail "ETL Utils cannot be used with open source only" }} + {{- else if .Values.etlUtils.fullImageName }} + image: {{ .Values.etlUtils.fullImageName }} + {{- else if .Values.kubecostModel.fullImageName }} + image: {{ .Values.kubecostModel.fullImageName }} + {{- else if .Values.imageVersion }} + image: {{ .Values.kubecostModel.image }}:{{ .Values.imageVersion }} + {{- else if eq "development" .Chart.AppVersion }} + image: gcr.io/kubecost1/cost-model-nightly:latest + {{- else }} + image: {{ .Values.kubecostModel.image }}:prod-{{ $.Chart.AppVersion }} + {{ end }} + {{- else }} + image: gcr.io/kubecost1/cost-model:prod-{{ $.Chart.AppVersion }} + {{ end }} + readinessProbe: + httpGet: + path: /healthz + port: 9006 + initialDelaySeconds: 10 + periodSeconds: 5 + failureThreshold: 200 + livenessProbe: + httpGet: + path: /healthz + port: 9006 + initialDelaySeconds: 10 + periodSeconds: 5 + imagePullPolicy: Always + args: ["etl-utils"] + ports: + - name: api + containerPort: 9006 + protocol: TCP + resources: + {{- toYaml .Values.etlUtils.resources | nindent 12 }} + volumeMounts: + {{- if .Values.etlUtils.thanosSourceBucketSecret }} + - name: etl-bucket-config + mountPath: /var/configs/etl + readOnly: true + {{- end }} + env: + - name: CONFIG_PATH + value: /var/configs/ + {{- if .Values.etlUtils.thanosSourceBucketSecret }} + - name: ETL_BUCKET_CONFIG + value: "/var/configs/etl/object-store.yaml" + {{- end}} + {{- range $key, $value := .Values.etlUtils.env }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + + {{- if .Values.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.imagePullSecrets | indent 2 }} + {{- end }} + {{- with .Values.etlUtils.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.etlUtils.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.etlUtils.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/etl-utils-service.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/etl-utils-service.yaml new file mode 100644 index 000000000..8296d7faa --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/etl-utils-service.yaml @@ -0,0 +1,18 @@ +{{- if .Values.etlUtils.enabled }} + +kind: Service +apiVersion: v1 +metadata: + name: {{ template "etlUtils.serviceName" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "etlUtils.commonLabels" . | nindent 4 }} +spec: + selector: +{{ include "etlUtils.selectorLabels" . | nindent 4 }} + type: "ClusterIP" + ports: + - name: api + port: 9006 + targetPort: 9006 +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/external-grafana-config-map-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/external-grafana-config-map-template.yaml new file mode 100644 index 000000000..1ac24ee3e --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/external-grafana-config-map-template.yaml @@ -0,0 +1,11 @@ +{{- if eq .Values.global.grafana.proxy false -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: external-grafana-config-map + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + grafanaURL: {{ .Values.global.grafana.scheme | default "http" }}://{{- .Values.global.grafana.domainName }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/extra-manifests.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/extra-manifests.yaml new file mode 100644 index 000000000..edad397d9 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/extra-manifests.yaml @@ -0,0 +1,8 @@ +{{ range .Values.extraObjects }} +--- +{{- if typeIs "string" . }} + {{- tpl . $ }} +{{- else }} + {{- tpl (toYaml .) $ }} +{{- end }} +{{ end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/forecasting-deployment.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/forecasting-deployment.yaml new file mode 100644 index 000000000..acc8a3c7d --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/forecasting-deployment.yaml @@ -0,0 +1,145 @@ +{{- if and .Values.forecasting.enabled (not .Values.federatedETL.agentOnly) }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "forecasting.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "forecasting.commonLabels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "forecasting.selectorLabels" . | nindent 6 }} + strategy: + type: RollingUpdate + template: + metadata: + labels: + app.kubernetes.io/name: forecasting + app.kubernetes.io/instance: {{ .Release.Name }} + app: forecasting + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.global.podAnnotations}} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + automountServiceAccountToken: false + {{- if .Values.global.platforms.openshift.enabled }} + securityContext: + {{- toYaml .Values.global.platforms.openshift.securityContext | nindent 8 }} + {{- else if .Values.global.securityContext }} + securityContext: + {{- toYaml .Values.global.securityContext | nindent 8 }} + {{- else }} + securityContext: + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + {{- end }} + restartPolicy: Always + containers: + - name: forecasting + {{- if .Values.forecasting.fullImageName }} + image: {{ .Values.forecasting.fullImageName }} + {{- else }} + image: gcr.io/kubecost1/kubecost-modeling:prod-{{ $.Chart.AppVersion }} + {{ end }} + {{- if .Values.forecasting.readinessProbe.enabled }} + volumeMounts: + - name: tmp + {{- /* In the future, this path should be configurable and not under tmp */}} + mountPath: /tmp + securityContext: + {{- toYaml .Values.global.containerSecurityContext | nindent 12 }} + {{- if .Values.forecasting.imagePullPolicy }} + imagePullPolicy: {{ .Values.forecasting.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + ports: + - name: tcp-api + containerPort: 5000 + protocol: TCP + {{- with .Values.forecasting.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + env: + - name: CONFIG_PATH + value: /var/configs/ + {{- if or .Values.saml.enabled .Values.oidc.enabled }} + - name: KCM_BASE_URL + value: http://{{ template "aggregator.serviceName" . }}:9008 + {{- else }} + - name: KCM_BASE_URL + value: http://{{ template "aggregator.serviceName" . }}:9004 + {{- end }} + - name: MODEL_STORAGE_PATH + value: "/tmp/localrun/models" + - name: PAGE_ITEM_LIMIT + value: "1000" + {{- range $key, $value := .Values.forecasting.env }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + readinessProbe: + httpGet: + path: /healthz + port: 5000 + initialDelaySeconds: {{ .Values.forecasting.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.forecasting.readinessProbe.periodSeconds }} + failureThreshold: {{ .Values.forecasting.readinessProbe.failureThreshold }} + {{- end }} + {{- if .Values.forecasting.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: /healthz + port: 5000 + initialDelaySeconds: {{ .Values.forecasting.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.forecasting.livenessProbe.periodSeconds }} + failureThreshold: {{ .Values.forecasting.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.forecasting.priority }} + {{- if .Values.forecasting.priority.enabled }} + {{- if .Values.forecasting.priority.name }} + priorityClassName: {{ .Values.forecasting.priority.name }} + {{- else }} + priorityClassName: {{ template "forecasting.fullname" . }}-priority + {{- end }} + {{- end }} + {{- end }} + {{- with .Values.forecasting.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.forecasting.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.forecasting.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: tmp + {{- /* + An emptyDir for models is necessary because of the + readOnlyRootFilesystem default In the future, this may optionally be a + PV. To allow Python to auto-detect a temp directory, which the code + currently relies on, we mount it at /tmp. In the future this will be a + configurable path. + */}} + emptyDir: + sizeLimit: 500Mi +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/forecasting-service.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/forecasting-service.yaml new file mode 100644 index 000000000..41e69961e --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/forecasting-service.yaml @@ -0,0 +1,17 @@ +{{- if and .Values.forecasting.enabled (not .Values.federatedETL.agentOnly) }} +kind: Service +apiVersion: v1 +metadata: + name: {{ template "forecasting.serviceName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "forecasting.commonLabels" . | nindent 4 }} +spec: + selector: + {{- include "forecasting.selectorLabels" . | nindent 4 }} + type: ClusterIP + ports: + - name: tcp-api + port: 5000 + targetPort: 5000 +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/frontend-deployment-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/frontend-deployment-template.yaml new file mode 100644 index 000000000..8ba47e87e --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/frontend-deployment-template.yaml @@ -0,0 +1,218 @@ +{{- if eq (include "frontend.deployMethod" .) "haMode" }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "frontend.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if and .Values.kubecostDeployment .Values.kubecostDeployment.labels }} + {{- toYaml .Values.kubecostDeployment.labels | nindent 4 }} + {{- end }} + {{- if and .Values.kubecostDeployment .Values.kubecostDeployment.annotations }} + annotations: + {{- toYaml .Values.kubecostDeployment.annotations | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.kubecostFrontend.haReplicas | default 2 }} + selector: + matchLabels: + {{- include "frontend.selectorLabels" . | nindent 6 }} + {{- if .Values.kubecostFrontend.deploymentStrategy }} + {{- with .Values.kubecostFrontend.deploymentStrategy }} + strategy: {{ toYaml . | nindent 4 }} + {{- end }} + {{- else }} + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + {{- end }} + template: + metadata: + labels: + {{/* + Force pod restarts on upgrades to ensure the nginx config is current + */}} + {{- if not .Values.global.platforms.cicd.enabled }} + helm-rollout-restarter: {{ randAlphaNum 5 | quote }} + {{- end }} + {{- include "frontend.selectorLabels" . | nindent 8 }} + {{- if .Values.global.additionalLabels }} + {{- toYaml .Values.global.additionalLabels | nindent 8 }} + {{- end }} + {{- if and .Values.kubecostDeployment .Values.kubecostDeployment.labels }} + {{- toYaml .Values.kubecostDeployment.labels | nindent 8 }} + {{- end }} + {{- with .Values.global.podAnnotations}} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- if .Values.global.platforms.openshift.enabled }} + securityContext: + {{- toYaml .Values.global.platforms.openshift.securityContext | nindent 8 }} + {{- else if .Values.global.securityContext }} + securityContext: + {{- toYaml .Values.global.securityContext | nindent 8 }} + {{- else }} + securityContext: + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + {{- end }} + restartPolicy: Always + serviceAccountName: {{ template "cost-analyzer.serviceAccountName" . }} + volumes: + - name: tmp + emptyDir: {} + - name: nginx-conf + configMap: + name: nginx-conf + items: + - key: nginx.conf + path: default.conf + {{- if .Values.global.containerSecuritycontext }} + - name: var-run + emptyDir: {} + - name: cache + emptyDir: {} + {{- end }} + {{- if .Values.kubecostFrontend.tls }} + {{- if .Values.kubecostFrontend.tls.enabled }} + - name: tls + secret: + secretName : {{ .Values.kubecostFrontend.tls.secretName }} + items: + - key: tls.crt + path: kc.crt + - key: tls.key + path: kc.key + {{- end }} + {{- end }} + {{- if .Values.kubecostAdmissionController }} + {{- if .Values.kubecostAdmissionController.enabled }} + {{- if .Values.kubecostAdmissionController.secretName }} + - name: webhook-server-tls + secret: + secretName: {{ .Values.kubecostAdmissionController.secretName }} + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + {{- end }} + {{- end }} + {{- end }} + containers: + {{- if .Values.kubecostFrontend }} + {{- if .Values.kubecostFrontend.fullImageName }} + - image: {{ .Values.kubecostFrontend.fullImageName }} + {{- else if .Values.imageVersion }} + - image: {{ .Values.kubecostFrontend.image }}:{{ .Values.imageVersion }} + {{- else if eq "development" .Chart.AppVersion }} + - image: gcr.io/kubecost1/frontend-nightly:latest + {{- else }} + - image: {{ .Values.kubecostFrontend.image }}:prod-{{ $.Chart.AppVersion }} + {{- end }} + {{- else }} + - image: gcr.io/kubecost1/frontend:prod-{{ $.Chart.AppVersion }} + {{- end }} + name: cost-analyzer-frontend + ports: + - name: tcp-frontend + containerPort: 9090 + protocol: TCP + env: + - name: GET_HOSTS_FROM + value: dns + {{- if .Values.kubecostFrontend.extraEnv -}} + {{ toYaml .Values.kubecostFrontend.extraEnv | nindent 12 }} + {{- end }} + {{- if .Values.kubecostFrontend.securityContext }} + securityContext: + {{- toYaml .Values.kubecostFrontend.securityContext | nindent 12 }} + {{- else }} + securityContext: + {{- toYaml .Values.global.containerSecurityContext | nindent 12 }} + {{- end }} + volumeMounts: + - name: tmp + mountPath: /tmp + - name: nginx-conf + mountPath: /etc/nginx/conf.d/ + {{- if .Values.global.containerSecuritycontext }} + - mountPath: /var/cache/nginx + name: cache + - mountPath: /var/run + name: var-run + {{- end }} + {{- if .Values.kubecostFrontend.tls }} + {{- if .Values.kubecostFrontend.tls.enabled }} + - name: tls + mountPath: /etc/ssl/certs + {{- end }} + {{- end }} + resources: + {{- toYaml .Values.kubecostFrontend.resources | nindent 12 }} + {{- if .Values.kubecostFrontend.imagePullPolicy }} + imagePullPolicy: {{ .Values.kubecostFrontend.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + {{- if .Values.kubecostFrontend.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: /healthz + port: 9090 + initialDelaySeconds: {{ .Values.kubecostFrontend.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.kubecostFrontend.readinessProbe.periodSeconds }} + failureThreshold: {{ .Values.kubecostFrontend.readinessProbe.failureThreshold }} + {{- end }} + {{- if .Values.kubecostFrontend.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: /healthz + port: 9090 + initialDelaySeconds: {{ .Values.kubecostFrontend.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.kubecostFrontend.livenessProbe.periodSeconds }} + failureThreshold: {{ .Values.kubecostFrontend.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.global.containerSecuritycontext }} + securityContext: + {{- toYaml .Values.global.containerSecuritycontext | nindent 12 }} + {{- end }} + {{- if .Values.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.priority }} + {{- if .Values.priority.enabled }} + {{- if gt (len .Values.priority.name) 0 }} + priorityClassName: {{ .Values.priority.name }} + {{- else }} + priorityClassName: {{ template "cost-analyzer.fullname" . }}-priority + {{- end }} + {{- end }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/frontend-service-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/frontend-service-template.yaml new file mode 100644 index 000000000..22c2d4fde --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/frontend-service-template.yaml @@ -0,0 +1,53 @@ +{{- if eq (include "frontend.deployMethod" .) "haMode" }} +kind: Service +apiVersion: v1 +metadata: + name: {{ template "frontend.serviceName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +{{- if .Values.service.labels }} +{{ toYaml .Values.service.labels | indent 4 }} +{{- end }} +{{- if .Values.service.annotations }} + annotations: +{{ toYaml .Values.service.annotations | indent 4 }} +{{- end }} +spec: + selector: + {{- include "frontend.selectorLabels" . | nindent 4 }} +{{- if .Values.service -}} +{{- if .Values.service.type }} + type: "{{ .Values.service.type }}" +{{- else }} + type: ClusterIP +{{- end }} +{{- else }} + type: ClusterIP +{{- end }} +{{- if (eq .Values.service.type "LoadBalancer") }} + {{- if .Values.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: +{{ toYaml .Values.service.loadBalancerSourceRanges | indent 4 }} + {{- end -}} +{{- end }} + ports: + - name: tcp-frontend + {{- if (eq .Values.service.type "NodePort") }} + {{- if .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + {{- end }} + port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} +{{- if .Values.service.sessionAffinity.enabled }} + sessionAffinity: ClientIP + {{- if .Values.service.sessionAffinity.timeoutSeconds }} + sessionAffinityConfig: + clientIP: + timeoutSeconds: {{ .Values.service.sessionAffinity.timeoutSeconds }} + {{- end }} +{{- else }} + sessionAffinity: None +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/gcpstore-config-map-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/gcpstore-config-map-template.yaml new file mode 100644 index 000000000..0c5da0df9 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/gcpstore-config-map-template.yaml @@ -0,0 +1,61 @@ +{{- if .Values.global.gcpstore.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: ubbagent-config +data: + config.yaml: | + # The identity section contains authentication information used + # by the agent. + identities: + - name: gcp + gcp: + # This parameter accepts a base64-encoded JSON service + # account key. The value comes from the reporting secret. + encodedServiceAccountKey: $AGENT_ENCODED_KEY + + # The metrics section defines the metric that will be reported. + # Metric names should match verbatim the identifiers created + # during pricing setup. + metrics: + + - name: commercial_ent_node_hr + type: int + endpoints: + - name: servicecontrol + + # The passthrough marker indicates that no aggregation should + # occur for this metric. Reports received are immediately sent + # to the reporting endpoint. We use passthrough for the + # instance_time metric since reports are generated + # automatically by a heartbeat source defined in a later + # section. + passthrough: {} + + # The endpoints section defines where metering data is ultimately + # sent. Currently supported endpoints include: + # * disk - some directory on the local filesystem + # * servicecontrol - Google Service Control + endpoints: + - name: servicecontrol + servicecontrol: + identity: gcp + # The service name is unique to your application and will be + # provided during onboarding. + serviceName: kubecost-ent.endpoints.kubecost-public.cloud.goog + consumerId: $AGENT_CONSUMER_ID # From the reporting secret. + + + # The sources section lists metric data sources run by the agent + # itself. The currently-supported source is 'heartbeat', which + # sends a defined value to a metric at a defined interval. In + # this example, the heartbeat sends a 60-second value through the + # "instance_time" metric every minute. + sources: + - name: commercial_ent_node_hr_heartbeat + heartbeat: + metric: commercial_ent_node_hr + intervalSeconds: 3600 + value: + int64Value: 1 +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-clusterrole.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-clusterrole.yaml new file mode 100644 index 000000000..ca1666823 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-clusterrole.yaml @@ -0,0 +1,24 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +{{- if .Values.grafana.rbac.create }} +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- with .Values.grafana.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} + name: {{ template "grafana.fullname" . }}-clusterrole +{{- if or .Values.grafana.sidecar.dashboards.enabled .Values.grafana.sidecar.datasources.enabled }} +rules: +- apiGroups: [""] # "" indicates the core API group + resources: ["configmaps"] + verbs: ["get", "watch", "list"] +{{- else }} +rules: [] +{{- end}} +{{- end}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-clusterrolebinding.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-clusterrolebinding.yaml new file mode 100644 index 000000000..4fc7267f3 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-clusterrolebinding.yaml @@ -0,0 +1,24 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +{{- if .Values.grafana.rbac.create }} +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ template "grafana.fullname" . }}-clusterrolebinding + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- with .Values.grafana.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +subjects: + - kind: ServiceAccount + name: {{ template "grafana.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +roleRef: + kind: ClusterRole + name: {{ template "grafana.fullname" . }}-clusterrole + apiGroup: rbac.authorization.k8s.io +{{- end}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-configmap-dashboard-provider.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-configmap-dashboard-provider.yaml new file mode 100644 index 000000000..78c7717be --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-configmap-dashboard-provider.yaml @@ -0,0 +1,28 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +{{- if .Values.grafana.sidecar.dashboards.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- with .Values.grafana.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} + name: {{ template "grafana.fullname" . }}-config-dashboards + namespace: {{ .Release.Namespace }} +data: + provider.yaml: |- + apiVersion: 1 + providers: + - name: 'default' + orgId: 1 + folder: '' + type: file + disableDeletion: false + options: + path: {{ .Values.grafana.sidecar.dashboards.folder }} +{{- end}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-configmap.yaml new file mode 100644 index 000000000..04d614667 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-configmap.yaml @@ -0,0 +1,90 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "grafana.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +data: +{{- if .Values.grafana.plugins }} + plugins: {{ join "," .Values.grafana.plugins }} +{{- end }} + grafana.ini: | +{{- range $key, $value := index .Values.grafana "grafana.ini" }} + [{{ $key }}] + {{- range $elem, $elemVal := $value }} + {{ $elem }} = {{ $elemVal }} + {{- end }} +{{- end }} + +{{- if .Values.grafana.datasources }} + {{- range $key, $value := .Values.grafana.datasources }} + {{ $key }}: | +{{ toYaml $value | trim | indent 4 }} + {{- end -}} +{{- end }} +{{- if not .Values.grafana.datasources }} + datasources.yaml: | + apiVersion: 1 + datasources: +{{- if .Values.global.prometheus.enabled }} + - access: proxy + isDefault: true + name: Prometheus + type: prometheus + url: http://{{ template "cost-analyzer.prometheus.server.name" . }}.{{ .Release.Namespace }}.svc + jsonData: + httpMethod: POST + prometheusType: Prometheus + prometheusVersion: 2.35.0 + timeInterval: 1m +{{- else }} + - access: proxy + isDefault: true + name: Prometheus + type: prometheus + url: {{ .Values.global.prometheus.fqdn }} + jsonData: + httpMethod: POST + prometheusType: Prometheus + prometheusVersion: 2.35.0 + timeInterval: 1m +{{- end -}} +{{- end }} +{{- if .Values.grafana.dashboardProviders }} + {{- range $key, $value := .Values.grafana.dashboardProviders }} + {{ $key }}: | +{{ toYaml $value | indent 4 }} + {{- end -}} +{{- end -}} + +{{- if .Values.grafana.dashboards }} + download_dashboards.sh: | + #!/usr/bin/env sh + set -euf + {{- if .Values.grafana.dashboardProviders }} + {{- range $key, $value := .Values.grafana.dashboardProviders }} + {{- range $value.providers }} + mkdir -p {{ .options.path }} + {{- end }} + {{- end }} + {{- end }} + + {{- range $provider, $dashboards := .Values.grafana.dashboards }} + {{- range $key, $value := $dashboards }} + {{- if (or (hasKey $value "gnetId") (hasKey $value "url")) }} + curl -sk \ + --connect-timeout 60 \ + --max-time 60 \ + -H "Accept: application/json" \ + -H "Content-Type: application/json;charset=UTF-8" \ + {{- if $value.url -}}{{ $value.url }}{{- else -}} https://grafana.com/api/dashboards/{{ $value.gnetId }}/revisions/{{- if $value.revision -}}{{ $value.revision }}{{- else -}}1{{- end -}}/download{{- end -}}{{ if $value.datasource }}| sed 's|\"datasource\":[^,]*|\"datasource\": \"{{ $value.datasource }}\"|g'{{ end }} \ + > /var/lib/grafana/dashboards/{{ $provider }}/{{ $key }}.json + {{- end }} + {{- end }} + {{- end }} +{{- end }} +{{ end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-attached-disks.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-attached-disks.yaml new file mode 100644 index 000000000..380964046 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-attached-disks.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-attached-disk-metrics + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + attached-disks.json: |- +{{- .Files.Get "grafana-dashboards/attached-disks.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-cluster-metrics-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-cluster-metrics-template.yaml new file mode 100644 index 000000000..729869176 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-cluster-metrics-template.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-cluster-metrics + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + cluster-metrics.json: |- +{{- .Files.Get "grafana-dashboards/cluster-metrics.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-cluster-utilization-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-cluster-utilization-template.yaml new file mode 100644 index 000000000..2cdbd394c --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-cluster-utilization-template.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-cluster-utilization + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + cluster-utilization.json: |- +{{- .Files.Get "grafana-dashboards/cluster-utilization.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-deployment-utilization-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-deployment-utilization-template.yaml new file mode 100644 index 000000000..f12d1095b --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-deployment-utilization-template.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-deployment-utilization + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + deployment-utilization.json: |- +{{- .Files.Get "grafana-dashboards/deployment-utilization.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-kubernetes-resource-efficiency-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-kubernetes-resource-efficiency-template.yaml new file mode 100644 index 000000000..60ad32d43 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-kubernetes-resource-efficiency-template.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-kubernetes-resource-efficiency + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + kubernetes-resource-efficiency.json: |- +{{- .Files.Get "grafana-dashboards/kubernetes-resource-efficiency.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-label-cost-utilization-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-label-cost-utilization-template.yaml new file mode 100644 index 000000000..e08092459 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-label-cost-utilization-template.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-label-cost + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + label-cost-utilization.json: |- +{{- .Files.Get "grafana-dashboards/label-cost-utilization.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-namespace-utilization-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-namespace-utilization-template.yaml new file mode 100644 index 000000000..f6d28686b --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-namespace-utilization-template.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-namespace-utilization + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + namespace-utilization.json: |- +{{- .Files.Get "grafana-dashboards/namespace-utilization.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-network-cloud-sevices.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-network-cloud-sevices.yaml new file mode 100644 index 000000000..af72b6664 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-network-cloud-sevices.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-network-cloud-services + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + grafana-network-cloud-services.json: |- +{{- .Files.Get "grafana-dashboards/network-cloud-services.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-network-costs.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-network-costs.yaml new file mode 100644 index 000000000..2e753745d --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-network-costs.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-network-costs-metrics + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + networkCosts-metrics.json: |- +{{- .Files.Get "grafana-dashboards/networkCosts-metrics.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-node-utilization-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-node-utilization-template.yaml new file mode 100644 index 000000000..8f2998c25 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-node-utilization-template.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-node-utilization + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + node-utilization.json: |- +{{- .Files.Get "grafana-dashboards/node-utilization.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-pod-utilization-multi-cluster.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-pod-utilization-multi-cluster.yaml new file mode 100644 index 000000000..7b8b6ae7a --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-pod-utilization-multi-cluster.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-pod-utilization-multi-cluster + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + pod-utilization-multi-cluster.json: |- +{{- .Files.Get "grafana-dashboards/pod-utilization-multi-cluster.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-pod-utilization-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-pod-utilization-template.yaml new file mode 100644 index 000000000..04374ff43 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-pod-utilization-template.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-pod-utilization + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + pod-utilization.json: |- +{{- .Files.Get "grafana-dashboards/pod-utilization.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-prometheus-metrics-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-prometheus-metrics-template.yaml new file mode 100644 index 000000000..723767c97 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-prometheus-metrics-template.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-prom-benchmark + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + prom-benchmark.json: |- +{{- .Files.Get "grafana-dashboards/prom-benchmark.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-workload-aggregator.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-workload-aggregator.yaml new file mode 100644 index 000000000..40dfb558b --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-workload-aggregator.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-workload-aggregator + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + workload-metrics-aggregator.json: |- +{{- .Files.Get "grafana-dashboards/workload-metrics-aggregator.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-workload-metrics.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-workload-metrics.yaml new file mode 100644 index 000000000..fa027dce7 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboard-workload-metrics.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-workload-metrics + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + grafana-workload-metrics.json: |- +{{- .Files.Get "grafana-dashboards/workload-metrics.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboards-json-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboards-json-configmap.yaml new file mode 100644 index 000000000..b7ccb3cb5 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-dashboards-json-configmap.yaml @@ -0,0 +1,24 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +{{- if .Values.grafana.dashboards }} + {{- range $provider, $dashboards := .Values.grafana.dashboards }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "grafana.fullname" $ }}-dashboards-{{ $provider }} + namespace: {{ $.Release.Namespace }} + labels: + app: {{ template "grafana.name" $ }} + release: {{ $.Release.Name }} + heritage: {{ $.Release.Service }} + dashboard-provider: {{ $provider }} +data: + {{- range $key, $value := $dashboards }} + {{- if hasKey $value "json" }} + {{ $key }}.json: | +{{ $value.json | indent 4 }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-datasource-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-datasource-template.yaml new file mode 100644 index 000000000..ba4ecea8c --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-datasource-template.yaml @@ -0,0 +1,38 @@ +{{- if .Values.grafana -}} +{{- if .Values.grafana.sidecar -}} +{{- if .Values.grafana.sidecar.datasources -}} +{{- if .Values.grafana.sidecar.datasources.enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-datasource + {{- if $.Values.grafana.namespace_datasources }} + namespace: {{ $.Values.grafana.namespace_datasources }} + {{- end }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.datasources.label }} + {{ $.Values.grafana.sidecar.datasources.label }}: "1" + {{- else }} + {{- if .Values.global.grafana.enabled }} + kubecost_grafana_datasource: "1" + {{- else }} + grafana_datasource: "1" + {{- end }} + {{- end }} +data: + {{ default "datasource.yaml" .Values.grafana.sidecar.datasources.dataSourceFilename }}: |- + apiVersion: 1 + datasources: + - access: proxy + name: default-kubecost + type: prometheus +{{- if .Values.grafana.sidecar.datasources.defaultDatasourceEnabled }} + isDefault: true +{{- else }} + isDefault: false +{{- end }} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-deployment.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-deployment.yaml new file mode 100644 index 000000000..63598d6dd --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-deployment.yaml @@ -0,0 +1,313 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "grafana.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- with .Values.grafana.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: + replicas: {{ .Values.grafana.replicas }} + selector: + matchLabels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + strategy: + type: {{ .Values.grafana.deploymentStrategy }} + {{- if ne .Values.grafana.deploymentStrategy "RollingUpdate" }} + rollingUpdate: null + {{- end }} + template: + metadata: + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + {{- if .Values.global.additionalLabels }} + {{ toYaml .Values.global.additionalLabels | nindent 8 }} + {{- end }} + {{- with .Values.grafana.podAnnotations }} + annotations: + {{ toYaml . | indent 8 }} + {{- end }} + spec: + serviceAccountName: {{ template "grafana.serviceAccountName" . }} + {{- if .Values.grafana.schedulerName }} + schedulerName: "{{ .Values.grafana.schedulerName }}" + {{- end }} + {{- if .Values.grafana.securityContext }} + securityContext: + {{- toYaml .Values.grafana.securityContext | nindent 8 }} + {{- else if and (.Values.global.platforms.openshift.enabled) (.Values.global.platforms.openshift.securityContext) }} + securityContext: + {{- toYaml .Values.global.platforms.openshift.securityContext | nindent 8 }} + {{- else if .Values.global.securityContext }} + securityContext: + {{- toYaml .Values.global.securityContext | nindent 8 }} + {{- end }} + {{- if .Values.grafana.priorityClassName }} + priorityClassName: "{{ .Values.grafana.priorityClassName }}" + {{- end }} + {{- if .Values.grafana.dashboards }} + initContainers: + - name: download-dashboards + image: "{{ .Values.grafana.downloadDashboardsImage.repository }}:{{ .Values.grafana.downloadDashboardsImage.tag }}" + imagePullPolicy: {{ .Values.grafana.downloadDashboardsImage.pullPolicy }} + command: ["sh", "/etc/grafana/download_dashboards.sh"] + {{- with .Values.global.containerSecurityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: config + mountPath: "/etc/grafana/download_dashboards.sh" + subPath: download_dashboards.sh + - name: storage + mountPath: "/var/lib/grafana" + {{- if .Values.grafana.persistence.subPath }} + subPath: {{ .Values.grafana.persistence.subPath }} + {{- end }} + {{- range .Values.grafana.extraSecretMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- end }} + {{- if .Values.grafana.image.pullSecrets }} + imagePullSecrets: + {{- range .Values.grafana.image.pullSecrets }} + - name: {{ . }} + {{- end}} + {{- end }} + containers: + {{- if .Values.grafana.sidecar.dashboards.enabled }} + - name: {{ template "grafana.name" . }}-sc-dashboard + image: "{{ .Values.grafana.sidecar.image.repository }}:{{ .Values.grafana.sidecar.image.tag }}" + imagePullPolicy: {{ .Values.grafana.sidecar.image.pullPolicy }} + {{- if .Values.global.containerSecurityContext }} + securityContext: + {{- toYaml .Values.global.containerSecurityContext | nindent 12 -}} + {{- end }} + env: + - name: LABEL + value: "{{ .Values.grafana.sidecar.dashboards.label }}" + - name: FOLDER + value: "{{ .Values.grafana.sidecar.dashboards.folder }}" + - name: ERROR_THROTTLE_SLEEP + value: "{{ .Values.grafana.sidecar.dashboards.error_throttle_sleep }}" + {{- with .Values.grafana.sidecar.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: sc-dashboard-volume + mountPath: {{ .Values.grafana.sidecar.dashboards.folder | quote }} + {{- end}} + {{- if .Values.grafana.sidecar.datasources.enabled }} + - name: {{ template "grafana.name" . }}-sc-datasources + image: "{{ .Values.grafana.sidecar.image.repository }}:{{ .Values.grafana.sidecar.image.tag }}" + imagePullPolicy: {{ .Values.grafana.sidecar.image.pullPolicy }} + {{- with .Values.global.containerSecurityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + env: + - name: LABEL + value: "{{ .Values.grafana.sidecar.datasources.label }}" + - name: FOLDER + value: "/etc/grafana/provisioning/datasources" + - name: ERROR_THROTTLE_SLEEP + value: "{{ .Values.grafana.sidecar.datasources.error_throttle_sleep }}" + resources: + {{ toYaml .Values.grafana.sidecar.resources | indent 12 }} + volumeMounts: + - name: sc-datasources-volume + mountPath: "/etc/grafana/provisioning/datasources" + {{- end}} + - name: grafana + image: "{{ .Values.grafana.image.repository }}:{{ .Values.grafana.image.tag }}" + imagePullPolicy: {{ .Values.grafana.image.pullPolicy }} + {{- with .Values.global.containerSecurityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: config + mountPath: "/etc/grafana/grafana.ini" + subPath: grafana.ini + - name: ldap + mountPath: "/etc/grafana/ldap.toml" + subPath: ldap.toml +{{- if .Values.grafana.dashboards }} + {{- range $provider, $dashboards := .Values.grafana.dashboards }} + {{- range $key, $value := $dashboards }} + {{- if hasKey $value "json" }} + - name: dashboards-{{ $provider }} + mountPath: "/var/lib/grafana/dashboards/{{ $provider }}/{{ $key }}.json" + subPath: "{{ $key }}.json" + {{- end }} + {{- end }} + {{- end }} +{{- end -}} +{{- if .Values.grafana.dashboardsConfigMaps }} + {{- range keys .Values.grafana.dashboardsConfigMaps }} + - name: dashboards-{{ . }} + mountPath: "/var/lib/grafana/dashboards/{{ . }}" + {{- end }} +{{- end }} +{{- if or (.Values.grafana.datasources) (include "cost-analyzer.grafanaEnabled" .) }} + - name: config + mountPath: "/etc/grafana/provisioning/datasources/datasources.yaml" + subPath: datasources.yaml +{{- end }} +{{- if .Values.grafana.dashboardProviders }} + - name: config + mountPath: "/etc/grafana/provisioning/dashboards/dashboardproviders.yaml" + subPath: dashboardproviders.yaml +{{- end }} +{{- if .Values.grafana.sidecar.dashboards.enabled }} + - name: sc-dashboard-volume + mountPath: {{ .Values.grafana.sidecar.dashboards.folder | quote }} + - name: sc-dashboard-provider + mountPath: "/etc/grafana/provisioning/dashboards/sc-dashboardproviders.yaml" + subPath: provider.yaml +{{- end}} +{{- if .Values.grafana.sidecar.datasources.enabled }} + - name: sc-datasources-volume + mountPath: "/etc/grafana/provisioning/datasources" +{{- end}} + - name: storage + mountPath: "/var/lib/grafana" + {{- if .Values.grafana.persistence.subPath }} + subPath: {{ .Values.grafana.persistence.subPath }} + {{- end }} + {{- range .Values.grafana.extraSecretMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + readOnly: {{ .readOnly }} + {{- end }} + ports: + - name: service + containerPort: {{ .Values.grafana.service.port }} + protocol: TCP + - name: grafana + containerPort: 3000 + protocol: TCP + env: + - name: GF_SECURITY_ADMIN_USER + valueFrom: + secretKeyRef: + name: {{ template "grafana.fullname" . }} + key: admin-user + - name: GF_SECURITY_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "grafana.fullname" . }} + key: admin-password + {{- if .Values.grafana.plugins }} + - name: GF_INSTALL_PLUGINS + valueFrom: + configMapKeyRef: + name: {{ template "grafana.fullname" . }} + key: plugins + {{- end }} + {{- if .Values.grafana.smtp.existingSecret }} + - name: GF_SMTP_USER + valueFrom: + secretKeyRef: + name: {{ .Values.grafana.smtp.existingSecret }} + key: user + - name: GF_SMTP_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.grafana.smtp.existingSecret }} + key: password + {{- end }} +{{- range $key, $value := .Values.grafana.env }} + - name: "{{ $key }}" + value: "{{ $value }}" +{{- end }} + {{- if .Values.grafana.envFromSecret }} + envFrom: + - secretRef: + name: {{ .Values.grafana.envFromSecret }} + {{- end }} + livenessProbe: +{{ toYaml .Values.grafana.livenessProbe | indent 12 }} + readinessProbe: +{{ toYaml .Values.grafana.readinessProbe | indent 12 }} + resources: +{{ toYaml .Values.grafana.resources | indent 12 }} + {{- with .Values.grafana.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.grafana.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.grafana.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + volumes: + - name: config + configMap: + name: {{ template "grafana.fullname" . }} + {{- if .Values.grafana.dashboards }} + {{- range keys .Values.grafana.dashboards }} + - name: dashboards-{{ . }} + configMap: + name: {{ template "grafana.fullname" $ }}-dashboards-{{ . }} + {{- end }} + {{- end }} + {{- if .Values.grafana.dashboardsConfigMaps }} + {{- range $provider, $name := .Values.grafana.dashboardsConfigMaps }} + - name: dashboards-{{ $provider }} + configMap: + name: {{ $name }} + {{- end }} + {{- end }} + - name: ldap + secret: + {{- if .Values.grafana.ldap.existingSecret }} + secretName: {{ .Values.grafana.ldap.existingSecret }} + {{- else }} + secretName: {{ template "grafana.fullname" . }} + {{- end }} + items: + - key: ldap-toml + path: ldap.toml + - name: storage + {{- if .Values.grafana.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.grafana.persistence.existingClaim | default (include "grafana.fullname" .) }} + {{- else }} + emptyDir: {} + {{- end -}} + {{- if .Values.grafana.sidecar.dashboards.enabled }} + - name: sc-dashboard-volume + emptyDir: {} + - name: sc-dashboard-provider + configMap: + name: {{ template "grafana.fullname" . }}-config-dashboards + {{- end }} + {{- if .Values.grafana.sidecar.datasources.enabled }} + - name: sc-datasources-volume + emptyDir: {} + {{- end -}} + {{- range .Values.grafana.extraSecretMounts }} + - name: {{ .name }} + secret: + secretName: {{ .secretName }} + defaultMode: {{ .defaultMode }} + {{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-ingress.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-ingress.yaml new file mode 100644 index 000000000..da2038170 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-ingress.yaml @@ -0,0 +1,47 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +{{- if .Values.grafana.ingress.enabled -}} +{{- $fullName := include "grafana.fullname" . -}} +{{- $servicePort := .Values.service.port -}} +{{- $ingressPath := .Values.ingress.path -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- if .Values.grafana.ingress.labels }} +{{ toYaml .Values.grafana.ingress.labels | indent 4 }} +{{- end }} +{{- with .Values.grafana.ingress.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: +{{- if .Values.grafana.ingress.tls }} + tls: + {{- range .Values.grafana.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} + rules: + {{- range .Values.grafana.ingress.hosts }} + - host: {{ . }} + http: + paths: + - path: {{ $ingressPath }} + pathType: {{ $.Values.grafana.ingress.pathType }} + backend: + service: + name: {{ $fullName }} + port: + number: {{ $servicePort }} + {{- end }} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-pvc.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-pvc.yaml new file mode 100644 index 000000000..d90e7f747 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-pvc.yaml @@ -0,0 +1,26 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +{{- if and .Values.grafana.persistence.enabled (not .Values.grafana.persistence.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ template "grafana.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + {{- with .Values.grafana.persistence.annotations }} + annotations: +{{ toYaml . | indent 4 }} + {{- end }} +spec: + accessModes: + {{- range .Values.grafana.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.grafana.persistence.size | quote }} + storageClassName: {{ .Values.grafana.persistence.storageClassName }} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-secret.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-secret.yaml new file mode 100644 index 000000000..df8b46dde --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-secret.yaml @@ -0,0 +1,22 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "grafana.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +type: Opaque +data: + admin-user: {{ .Values.grafana.adminUser | b64enc | quote }} + {{- if .Values.grafana.adminPassword }} + admin-password: {{ .Values.grafana.adminPassword | b64enc | quote }} + {{- else }} + admin-password: {{ randAlphaNum 40 | b64enc | quote }} + {{- end }} + {{- if not .Values.grafana.ldap.existingSecret }} + ldap-toml: {{ .Values.grafana.ldap.config | b64enc | quote }} + {{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-service.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-service.yaml new file mode 100644 index 000000000..3bf668ed8 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-service.yaml @@ -0,0 +1,51 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "grafana.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- if .Values.grafana.service.labels }} +{{ toYaml .Values.grafana.service.labels | indent 4 }} +{{- end }} +{{- with .Values.grafana.service.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: +{{- if (or (eq .Values.grafana.service.type "ClusterIP") (empty .Values.grafana.service.type)) }} + type: ClusterIP + {{- if .Values.grafana.service.clusterIP }} + clusterIP: {{ .Values.grafana.service.clusterIP }} + {{end}} +{{- else if eq .Values.grafana.service.type "LoadBalancer" }} + type: {{ .Values.grafana.service.type }} + {{- if .Values.grafana.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.grafana.service.loadBalancerIP }} + {{- end }} + {{- if .Values.grafana.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: +{{ toYaml .Values.grafana.service.loadBalancerSourceRanges | indent 4 }} + {{- end -}} +{{- else }} + type: {{ .Values.grafana.service.type }} +{{- end }} +{{- if .Values.grafana.service.externalIPs }} + externalIPs: +{{ toYaml .Values.grafana.service.externalIPs | indent 4 }} +{{- end }} + ports: + - name: tcp-service + port: {{ .Values.grafana.service.port }} + protocol: TCP + targetPort: 3000 +{{ if (and (eq .Values.grafana.service.type "NodePort") (not (empty .Values.grafana.service.nodePort))) }} + nodePort: {{.Values.grafana.service.nodePort}} +{{ end }} + selector: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-serviceaccount.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-serviceaccount.yaml new file mode 100644 index 000000000..bf2f21db6 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/grafana-serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +{{- if .Values.grafana.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app: {{ template "grafana.name" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + name: {{ template "grafana.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/install-plugins.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/install-plugins.yaml new file mode 100644 index 000000000..f2abf1c41 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/install-plugins.yaml @@ -0,0 +1,43 @@ +{{- if .Values.kubecostModel.plugins.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "cost-analyzer.fullname" . }}-install-plugins + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + install_plugins.sh: |- + {{- if .Values.kubecostModel.plugins.install.enabled }} + set -ex + rm -f {{ .Values.kubecostModel.plugins.folder }}/bin/* + mkdir -p {{ .Values.kubecostModel.plugins.folder }}/bin + cd {{ .Values.kubecostModel.plugins.folder }}/bin + OSTYPE=$(cat /etc/os-release) + OS='' + case "$OSTYPE" in + *Linux*) OS='linux';; + *) echo "$OSTYPE is unsupported" && exit 1 ;; + esac + + UNAME_OUTPUT=$(uname -m) + ARCH='' + case "$UNAME_OUTPUT" in + *x86_64*) ARCH='amd64';; + *amd64*) ARCH='amd64';; + *aarch64*) ARCH='arm64';; + *arm64*) ARCH='arm64';; + *) echo "$UNAME_OUTPUT is unsupported" && exit 1 ;; + esac + + {{- if .Values.kubecostModel.plugins.version }} + VER={{ .Values.kubecostModel.plugins.version | quote}} + {{- else }} + VER=$(curl --silent https://api.github.com/repos/opencost/opencost-plugins/releases/latest | grep ".tag_name" | awk -F\" '{print $4}') + {{- end }} + + {{- range $pluginName := .Values.kubecostModel.plugins.enabledPlugins }} + curl -fsSLO "https://github.com/opencost/opencost-plugins/releases/download/$VER/{{ $pluginName }}.ocplugin.$OS.$ARCH" + chmod a+rx "{{ $pluginName }}.ocplugin.$OS.$ARCH" + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/integrations-postgres-queries-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/integrations-postgres-queries-configmap.yaml new file mode 100644 index 000000000..5e0af3e00 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/integrations-postgres-queries-configmap.yaml @@ -0,0 +1,14 @@ +{{- if .Values.global.integrations.postgres.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: kubecost-integrations-postgres-queries + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + kubecost-queries.json: |- + {{- with .Values.global.integrations.postgres.queryConfigs }} + {{- . | toJson | nindent 6 }} + {{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/integrations-postgres-secret.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/integrations-postgres-secret.yaml new file mode 100644 index 000000000..136ab6016 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/integrations-postgres-secret.yaml @@ -0,0 +1,19 @@ +{{- if and (.Values.global.integrations.postgres.enabled) (eq .Values.global.integrations.postgres.databaseSecretName "") }} +apiVersion: v1 +kind: Secret +metadata: + name: kubecost-integrations-postgres + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +type: Opaque +stringData: + creds.json: |- + { + "host": "{{ .Values.global.integrations.postgres.databaseHost }}", + "port": "{{ .Values.global.integrations.postgres.databasePort }}", + "databaseName": "{{ .Values.global.integrations.postgres.databaseName }}", + "user": "{{ .Values.global.integrations.postgres.databaseUser }}", + "password": "{{ .Values.global.integrations.postgres.databasePassword }}" + } +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-admission-controller-service-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-admission-controller-service-template.yaml new file mode 100644 index 000000000..658dca3a9 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-admission-controller-service-template.yaml @@ -0,0 +1,15 @@ +{{- if .Values.kubecostAdmissionController -}} +{{- if .Values.kubecostAdmissionController.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: webhook-server + namespace: {{.Release.Namespace}} +spec: + selector: + {{ include "cost-analyzer.selectorLabels" . | nindent 4 }} + ports: + - port: 443 + targetPort: 8443 +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-admission-controller-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-admission-controller-template.yaml new file mode 100644 index 000000000..be68bcea1 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-admission-controller-template.yaml @@ -0,0 +1,30 @@ +{{- if .Values.kubecostAdmissionController -}} +{{- if .Values.kubecostAdmissionController.enabled -}} +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: kubecost-deployment-validation +webhooks: + - name: "kubecost-deployment-validation.kubecost.svc" + failurePolicy: Ignore + rules: + - operations: [ "CREATE", "UPDATE" ] + apiGroups: [ "apps" ] + apiVersions: [ "v1" ] + resources: [ "deployments" ] + scope: "*" + clientConfig: + service: + namespace: {{.Release.Namespace}} + name: webhook-server + path: "/validate" + {{- if .Values.kubecostAdmissionController.caBundle }} + caBundle: {{ .Values.kubecostAdmissionController.caBundle }} + {{- else }} + caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCRENDQWV5Z0F3SUJBZ0lVR3E2YkdOaEowVjRsb0NiWHhUa0pocWkwUnB3d0RRWUpLb1pJaHZjTkFRRUwKQlFBd0pqRWtNQ0lHQTFVRUF3d2JkMlZpYUc5dmF5MXpaWEoyWlhJdWEzVmlaV052YzNRdWMzWmpNQjRYRFRJegpNREl3T1RFNU1UVTFNbG9YRFRJME1EWXlNekU1TVRVMU1sb3dKakVrTUNJR0ExVUVBd3diZDJWaWFHOXZheTF6ClpYSjJaWEl1YTNWaVpXTnZjM1F1YzNaak1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0MKQVFFQXpvU2JBejBhZFJTdEN3eVRPSGd2S2VuQ29GbWE2OC9nYTFHZjVST2dXeGJhamhQRTZKbEtBcENwK1pzKwo2bHJzL2J3bkx5SDdoMUFJa1NmZ25EYlNadDJjdHRFSmhSd25vKy90WElMYk84WndRQTErYXpUQzVtSkluZVF3CktRMkErYy9CUnk3N3B0SnZIRStkTEllcWhRelV2M25nWUwvSDZaMUZPa20xUCtlR0FwSWxyVHVPV1ozUVhRYkMKemhOQXppRWNjL3o3RERBdlFBMlpIQ1I2OGl1V0ptd0RYZEdjWmEwenNVb1hDbGIvWXdiWFgvMlp2dklIbkdtawp5VTlZdEhxNVpscFZjT0V5MTVBWFVEOFZVUU1jVXQ5NkJvVThMMXJKbTZJK0E0YmFySEs5QjlxcjdzRmFaY2wvCnBncHZGd0NBaHZHYUM2VzA5UnM3T0NrdXh3SURBUUFCb3lvd0tEQW1CZ05WSFJFRUh6QWRnaHQzWldKb2IyOXIKTFhObGNuWmxjaTVyZFdKbFkyOXpkQzV6ZG1Nd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFDdVhNcUgzYmhsVApGKzlRUFplS2xiUTZlWSs0NlhMVGtEdlZzenAyZysweWhlMVNRRHZRUTVad1l6MnMwODNqb2loTXVzeFZ1TmFGCk1LdE9vbGY2bitsaUZFcEw4OU9XZ1VjdzJRdFdqVWUraU1zby91dWN0eGVPTzZLam9JcUVrUlg5YXh1cGxxVm0KakZRaGZtNlRYZ2pxWmttUVNsbHdLVkcxSFJZTkRveFpFa0JHK1l6RWF5QmdQdXl4bW5iTDdlck5IOVJQSVZtbAoxaWFnS1NVVG5vN0hJY3IwdHYzT3JEWDZRN3VJUGdWanBRSHMzNXBZSWlBYjVNR0RjWFZvY050SEZ0YnluREhzCi80WGhYMjFhOXdnSVF6dUF3ck0zQ0VDRnVocHJzWlZmQjBKQ1dBOG1aVEZneTVBL0tLUjJmTXRMRWRQS1ZsSXUKZjc1MjB3T3JzME09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + {{- end }} + admissionReviewVersions: ["v1"] + sideEffects: None + timeoutSeconds: 5 +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-agent-secret-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-agent-secret-template.yaml new file mode 100644 index 000000000..cda3c6055 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-agent-secret-template.yaml @@ -0,0 +1,12 @@ +{{- if .Values.agentKey }} +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: {{ .Values.agentKeySecretName }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + object-store.yaml: {{ .Values.agentKey }} +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-agent-secretprovider-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-agent-secretprovider-template.yaml new file mode 100644 index 000000000..3ebc1a4b6 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-agent-secretprovider-template.yaml @@ -0,0 +1,25 @@ +{{- if .Values.agent }} +{{- if ((.Values.agentCsi).enabled) }} +{{- if .Capabilities.APIVersions.Has "secrets-store.csi.x-k8s.io/v1" }} +apiVersion: secrets-store.csi.x-k8s.io/v1 +{{- else }} +apiVersion: secrets-store.csi.x-k8s.io/v1alpha1 +{{- end }} +kind: SecretProviderClass +metadata: + name: {{ .Values.agentCsi.secretProvider.name }} + namespace: {{ .Release.Namespace }} + labels: {{ unset (include "cost-analyzer.commonLabels" . | fromYaml) "app" | toYaml | nindent 4 }} + app: {{ template "kubecost.kubeMetricsName" . }} +spec: + provider: {{ required "Specify a valid provider." .Values.agentCsi.secretProvider.provider }} + {{- if .Values.agentCsi.secretProvider.parameters }} + parameters: + {{- .Values.agentCsi.secretProvider.parameters | toYaml | nindent 4 }} + {{- end }} + {{- if .Values.agentCsi.secretProvider.secretObjects }} + secretObjects: + {{- .Values.agentCsi.secretProvider.secretObjects | toYaml | nindent 2 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-cluster-controller-actions-config.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-cluster-controller-actions-config.yaml new file mode 100644 index 000000000..114f381b0 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-cluster-controller-actions-config.yaml @@ -0,0 +1,56 @@ +{{- if .Values.clusterController }} +{{- if .Values.clusterController.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: cluster-controller-continuous-cluster-sizing + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +{{- if .Values.clusterController.actionConfigs.clusterRightsize }} +binaryData: + config: | +{{- toJson .Values.clusterController.actionConfigs.clusterRightsize | b64enc | nindent 4 }} +{{- end }} +--- + +apiVersion: v1 +kind: ConfigMap +metadata: + name: cluster-controller-nsturndown-config + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +{{- if .Values.clusterController.actionConfigs.namespaceTurndown }} +binaryData: +{{- range .Values.clusterController.actionConfigs.namespaceTurndown }} + {{ .name }}: | + {{- toJson . | b64enc | nindent 4 }} +{{- end }} +{{- end }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cluster-controller-container-rightsizing-config + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +{{- if .Values.clusterController.actionConfigs.containerRightsize }} +binaryData: + config: | +{{- toJson .Values.clusterController.actionConfigs.containerRightsize | b64enc | nindent 4 }} +{{- end }} +{{- range .Values.clusterController.actionConfigs.clusterTurndown }} +--- +apiVersion: kubecost.com/v1alpha1 +kind: TurndownSchedule +metadata: + name: {{ .name }} +spec: + start: {{ .start }} + end: {{ .end }} + repeat: {{ .repeat }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-cluster-controller-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-cluster-controller-template.yaml new file mode 100644 index 000000000..ac86658be --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-cluster-controller-template.yaml @@ -0,0 +1,293 @@ +{{- if .Values.clusterController }} +{{- if .Values.clusterController.enabled }} +{{- $serviceName := include "cost-analyzer.serviceName" . -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "kubecost.clusterControllerName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +--- +# +# NOTE: +# The following ClusterRole permissions are only created and assigned for the +# cluster controller feature. They will not be added to any clusters by default. +# +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "kubecost.clusterControllerName" . }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +rules: + - apiGroups: + - kubecost.com + resources: + - turndownschedules + - turndownschedules/status + verbs: + - get + - list + - watch + - create + - patch + - update + - delete + - apiGroups: + - '' + - events.k8s.io + resources: + - events + verbs: + - create + - patch + - update + - apiGroups: + - '' + resources: + - deployments + - nodes + - pods + - resourcequotas + - replicationcontrollers + - limitranges + - pods/eviction + verbs: + - get + - list + - watch + - create + - patch + - update + - delete + - apiGroups: + - '' + resources: + - configmaps + verbs: + - get + - list + - watch + - update + - patch + - delete + - apiGroups: + - '' + resources: + - configmaps + - namespaces + - persistentvolumeclaims + - persistentvolumes + - endpoints + - events + - services + verbs: + - get + - list + - watch + - apiGroups: + - '' + resources: + - configmaps + resourceNames: + - 'cluster-controller-nsturndown-config' + verbs: + - get + - create + - update + - apiGroups: + - apps + resources: + - statefulsets + - deployments + - daemonsets + - replicasets + verbs: + - get + - list + - watch + - create + - patch + - update + - delete + - apiGroups: + - batch + resources: + - cronjobs + - jobs + verbs: + - get + - list + - watch + - create + - patch + - update + - delete + - apiGroups: + - autoscaling + resources: + - horizontalpodautoscalers + verbs: + - get + - list + - watch + - apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - get + - list + - watch + - apiGroups: + - storage.k8s.io + resources: + - storageclasses + verbs: + - get + - list + - watch + - apiGroups: + - events.k8s.io + resources: + - events + verbs: + - get + - list + - watch + # Used for namespace turndown + # When cleaning a namespace, we need the ability to remove + # arbitrary resources (since we helm uninstall all releases in that NS first) + {{- if .Values.clusterController.namespaceTurndown.rbac.enabled }} + - apiGroups: ["*"] + resources: ["*"] + verbs: + - list + - get + - delete + {{- end }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ template "kubecost.clusterControllerName" . }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "kubecost.clusterControllerName" . }} +subjects: + - kind: ServiceAccount + name: {{ template "kubecost.clusterControllerName" . }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "kubecost.clusterControllerName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +spec: + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + selector: + matchLabels: + app: {{ template "kubecost.clusterControllerName" . }} + template: + metadata: + labels: + app: {{ template "kubecost.clusterControllerName" . }} + {{- with .Values.global.podAnnotations}} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- if .Values.clusterController.priorityClassName }} + priorityClassName: "{{ .Values.clusterController.priorityClassName }}" + {{- end }} + containers: + - name: {{ template "kubecost.clusterControllerName" . }} + {{- if eq (typeOf .Values.clusterController.image) "string" }} + image: {{ .Values.clusterController.image }} + {{- else }} + image: {{ .Values.clusterController.image.repository }}:{{ .Values.clusterController.image.tag }} + {{- end}} + imagePullPolicy: {{ .Values.clusterController.imagePullPolicy }} + volumeMounts: + - name: cluster-controller-keys + mountPath: /var/keys + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: CLUSTER_ID + value: {{ .Values.prometheus.server.global.external_labels.cluster_id }} + - name: TURNDOWN_NAMESPACE + value: {{ .Release.Namespace }} + - name: TURNDOWN_DEPLOYMENT + value: {{ template "kubecost.clusterControllerName" . }} + - name: GOOGLE_APPLICATION_CREDENTIALS + value: /var/keys/service-key.json + - name: CC_LOG_LEVEL + value: {{ .Values.clusterController.logLevel | default "info" }} + - name: CC_KUBESCALER_COST_MODEL_PATH + value: http://{{ $serviceName }}.{{ .Release.Namespace }}:{{ .Values.service.targetPort | default 9090 }}/model + - name: CC_CCL_COST_MODEL_PATH + value: http://{{ $serviceName }}.{{ .Release.Namespace }}:{{ .Values.service.targetPort | default 9090 }}/model + {{- if .Values.clusterController.kubescaler }} + - name: CC_KUBESCALER_DEFAULT_RESIZE_ALL + value: {{ .Values.clusterController.kubescaler.defaultResizeAll | default "false" | quote }} + {{- end }} + ports: + - name: http-server + containerPort: 9731 + hostPort: 9731 + serviceAccount: {{ template "kubecost.clusterControllerName" . }} + serviceAccountName: {{ template "kubecost.clusterControllerName" . }} + {{- with .Values.clusterController.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: cluster-controller-keys + secret: + secretName: {{ .Values.clusterController.secretName | default "cluster-controller-service-key" }} + # The secret is optional because not all of cluster controller's + # functionality requires this secret. Cluster controller will + # partially or fully initialize based on the presence of these keys + # and their validity. + optional: true +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ template "kubecost.clusterControllerName" . }}-service + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +spec: + type: ClusterIP + ports: + - name: http + protocol: TCP + port: 9731 + targetPort: 9731 + selector: + app: {{ template "kubecost.clusterControllerName" . }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-cluster-manager-configmap-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-cluster-manager-configmap-template.yaml new file mode 100644 index 000000000..b851fd4e9 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-cluster-manager-configmap-template.yaml @@ -0,0 +1,14 @@ +{{- if .Values.kubecostProductConfigs }} +{{- if .Values.kubecostProductConfigs.clusters }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: kubecost-clusters + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + default-clusters.yaml: | +{{- toYaml .Values.kubecostProductConfigs.clusters | nindent 4 }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-metrics-deployment-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-metrics-deployment-template.yaml new file mode 100644 index 000000000..e93ae0f0d --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-metrics-deployment-template.yaml @@ -0,0 +1,341 @@ +{{- if .Values.kubecostMetrics }} +{{- if .Values.kubecostMetrics.exporter }} +{{- if or (or .Values.kubecostMetrics.exporter.enabled .Values.agent) .Values.cloudAgent }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "kubecost.kubeMetricsName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ unset (include "cost-analyzer.commonLabels" . | fromYaml) "app" | toYaml | nindent 4 }} + app: {{ template "kubecost.kubeMetricsName" . }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- with .Values.kubecostMetrics.exporter.labels }} +{{ toYaml . | indent 4 }} +{{- end }} +spec: + replicas: {{ .Values.kubecostMetrics.exporter.replicas | default 1 }} + selector: + matchLabels: + app: {{ include "kubecost.kubeMetricsName" . }} + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app: {{ template "kubecost.kubeMetricsName" . }} + {{- if .Values.global.additionalLabels }} + {{ toYaml .Values.global.additionalLabels | nindent 8 }} + {{- end }} +{{- with .Values.kubecostMetrics.exporter.labels }} +{{ toYaml . | indent 8 }} +{{- end }} + {{- with .Values.global.podAnnotations}} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- if .Values.kubecostFrontend.tls }} + {{- if .Values.kubecostFrontend.tls.enabled }} + securityContext: + runAsUser: 0 + {{- else }} + securityContext: + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + {{- end }} + {{- else }} + securityContext: + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + {{- end }} + restartPolicy: Always + serviceAccountName: {{ template "cost-analyzer.serviceAccountName" . }} + volumes: + {{- if .Values.agent }} + - name: config-store + {{- if ((.Values.agentCsi).enabled) }} + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + volumeAttributes: + secretProviderClass: "{{ .Values.agentCsi.secretProvider.name }}" + {{- else }} + secret: + secretName: {{ .Values.agentKeySecretName }} + {{- end }} + {{- end }} + {{- if .Values.kubecostProductConfigs }} + {{- if .Values.kubecostProductConfigs.gcpSecretName }} + - name: gcp-key-secret + secret: + secretName: {{ .Values.kubecostProductConfigs.gcpSecretName }} + items: + - key: {{ .Values.kubecostProductConfigs.gcpSecretKeyName | default "compute-viewer-kubecost-key.json" }} + path: service-key.json + {{- end }} + {{- if .Values.kubecostProductConfigs.serviceKeySecretName }} + - name: service-key-secret + secret: + secretName: {{ .Values.kubecostProductConfigs.serviceKeySecretName }} + {{- else if .Values.kubecostProductConfigs.createServiceKeySecret }} + - name: service-key-secret + secret: + secretName: cloud-service-key + {{- end }} + {{- if .Values.kubecostProductConfigs.azureStorageSecretName }} + - name: azure-storage-config + secret: + secretName: {{ .Values.kubecostProductConfigs.azureStorageSecretName }} + items: + - key: azure-storage-config.json + path: azure-storage-config.json + {{- else if .Values.kubecostProductConfigs.azureStorageCreateSecret }} + - name: azure-storage-config + secret: + secretName: azure-storage-config + {{- end }} + {{- if .Values.kubecostProductConfigs.cloudIntegrationSecret }} + - name: cloud-integration + secret: + secretName: {{ .Values.kubecostProductConfigs.cloudIntegrationSecret }} + items: + - key: cloud-integration.json + path: cloud-integration.json + {{- else if or .Values.kubecostProductConfigs.cloudIntegrationJSON ((.Values.kubecostProductConfigs).athenaBucketName) }} + - name: cloud-integration + secret: + secretName: cloud-integration + items: + - key: cloud-integration.json + path: cloud-integration.json + {{- end }} + {{- end }} + - name: persistent-configs +{{- if .Values.persistentVolume }} +{{- if .Values.persistentVolume.enabled }} + persistentVolumeClaim: +{{- if .Values.persistentVolume.existingClaim }} + claimName: {{ .Values.persistentVolume.existingClaim }} +{{- else }} + claimName: {{ template "cost-analyzer.fullname" . }} +{{- end -}} +{{- else }} + emptyDir: {} +{{- end -}} +{{- else }} + persistentVolumeClaim: + claimName: {{ template "cost-analyzer.fullname" . }} +{{- end }} + initContainers: +{{- if .Values.supportNFS }} + - name: config-db-perms-fix + {{- if .Values.initChownDataImage }} + image: {{ .Values.initChownDataImage }} + {{- else }} + image: busybox + {{- end }} + resources: +{{ toYaml .Values.initChownData.resources | indent 12 }} + command: ["sh", "-c", "/bin/chmod -R 777 /var/configs"] + volumeMounts: + - name: persistent-configs + mountPath: /var/configs + securityContext: + runAsUser: 0 +{{- end }} + containers: + {{- if .Values.kubecostModel }} + {{- if .Values.kubecostModel.fullImageName }} + - image: {{ .Values.kubecostModel.fullImageName }} + {{- else if .Values.imageVersion }} + - image: {{ .Values.kubecostModel.image }}:{{ .Values.imageVersion }} + {{- else if eq "development" .Chart.AppVersion }} + - image: gcr.io/kubecost1/cost-model-nightly:latest + {{- else }} + - image: {{ .Values.kubecostModel.image }}:prod-{{ $.Chart.AppVersion }} + {{ end }} + {{- else }} + - image: gcr.io/kubecost1/cost-model:prod-{{ $.Chart.AppVersion }} + {{ end }} + {{- if .Values.kubecostModel.imagePullPolicy }} + imagePullPolicy: {{ .Values.kubecostModel.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + name: {{ template "kubecost.kubeMetricsName" . }} + ports: + - name: tcp-metrics + protocol: TCP + containerPort: {{ .Values.kubecostMetrics.exporter.port | default 9005 }} + resources: +{{ toYaml .Values.kubecostMetrics.exporter.resources | indent 12 }} + readinessProbe: + httpGet: + path: /healthz + port: {{ .Values.kubecostMetrics.exporter.port | default 9005 }} + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 200 + volumeMounts: + - name: persistent-configs + mountPath: /var/configs + {{- if .Values.agent }} + - name: config-store + mountPath: /var/secrets + {{- end }} + {{- if .Values.kubecostProductConfigs }} + {{- if .Values.kubecostProductConfigs.gcpSecretName }} + - name: gcp-key-secret + mountPath: /var/secrets + {{- end }} + {{- if or .Values.kubecostProductConfigs.azureStorageSecretName .Values.kubecostProductConfigs.azureStorageCreateSecret }} + - name: azure-storage-config + mountPath: /var/azure-storage-config + {{- end }} + {{- if or (.Values.kubecostProductConfigs.cloudIntegrationSecret) (.Values.kubecostProductConfigs.cloudIntegrationJSON) ((.Values.kubecostProductConfigs).athenaBucketName) }} + - name: cloud-integration + mountPath: /var/configs/cloud-integration + {{- end }} + {{- if or .Values.kubecostProductConfigs.serviceKeySecretName .Values.kubecostProductConfigs.createServiceKeySecret }} + - name: service-key-secret + mountPath: /var/secrets + {{- end }} + {{- end }} + args: + {{- if .Values.cloudAgent }} + - cloud-agent + {{- else }} + - agent + {{- end }} + {{- if .Values.kubecostMetrics.exporter.extraArgs }} + {{ toYaml .Values.kubecostMetrics.exporter.extraArgs | nindent 12 }} + {{- end }} + env: + - name: PROMETHEUS_SERVER_ENDPOINT + valueFrom: + configMapKeyRef: + name: {{ template "cost-analyzer.fullname" . }} + key: prometheus-server-endpoint + {{- if .Values.cloudAgent }} + - name: CLOUD_AGENT_KEY + value: {{ .Values.cloudAgentKey }} + - name: CLOUD_REPORTING_SERVER + value: {{ .Values.cloudReportingServer }} + {{- end }} + - name: CLOUD_PROVIDER_API_KEY + value: "AIzaSyDXQPG_MHUEy9neR7stolq6l0ujXmjJlvk" # The GCP Pricing API requires a key. + {{- if .Values.kubecostProductConfigs }} + {{- if .Values.kubecostProductConfigs.gcpSecretName }} + - name: GOOGLE_APPLICATION_CREDENTIALS + value: /var/configs/key.json + {{- end }} + {{- end }} + - name: CONFIG_PATH + value: /var/configs/ + - name: KUBECOST_METRICS_PORT + value: {{ (quote .Values.kubecostMetrics.exporter.port) | default (quote 9005) }} + {{- if .Values.agent }} + - name: KUBECOST_CONFIG_BUCKET + value: /var/secrets/object-store.yaml + - name: EXPORT_CLUSTER_INFO_ENABLED + value: {{ (quote .Values.kubecostMetrics.exporter.exportClusterInfo) | default (quote true) }} + - name: EXPORT_CLUSTER_CACHE_ENABLED + value: {{ (quote .Values.kubecostMetrics.exporter.exportClusterCache) | default (quote true) }} + {{- end }} + - name: EMIT_POD_ANNOTATIONS_METRIC + value: {{ (quote .Values.kubecostMetrics.emitPodAnnotations) | default (quote false) }} + - name: EMIT_NAMESPACE_ANNOTATIONS_METRIC + value: {{ (quote .Values.kubecostMetrics.emitNamespaceAnnotations) | default (quote false) }} + - name: EMIT_KSM_V1_METRICS + value: {{ (quote .Values.kubecostMetrics.emitKsmV1Metrics) | default (quote true) }} + - name: EMIT_KSM_V1_METRICS_ONLY # ONLY emit KSM v1 metrics that do not exist in KSM 2 by default + value: {{ (quote .Values.kubecostMetrics.emitKsmV1MetricsOnly) | default (quote false) }} + - name: MAX_QUERY_CONCURRENCY + value: {{ (quote .Values.kubecostModel.maxQueryConcurrency) | default (quote 5) }} + {{- if .Values.global.prometheus.queryServiceBasicAuthSecretName}} + - name: DB_BASIC_AUTH_USERNAME + valueFrom: + secretKeyRef: + name: {{ .Values.global.prometheus.queryServiceBasicAuthSecretName }} + key: USERNAME + - name: DB_BASIC_AUTH_PW + valueFrom: + secretKeyRef: + name: {{ .Values.global.prometheus.queryServiceBasicAuthSecretName }} + key: PASSWORD + {{- end }} + {{- if .Values.global.prometheus.queryServiceBearerTokenSecretName }} + - name: DB_BEARER_TOKEN + valueFrom: + secretKeyRef: + name: {{ .Values.global.prometheus.queryServiceBearerTokenSecretName }} + key: TOKEN + {{- end }} + {{- if .Values.global.prometheus.insecureSkipVerify }} + - name: INSECURE_SKIP_VERIFY + value: {{ (quote .Values.global.prometheus.insecureSkipVerify) }} + {{- end }} + {{- if .Values.cloudAgentClusterId }} + - name: CLUSTER_ID + value: {{ .Values.cloudAgentClusterId }} + {{- else if and (.Values.prometheus.server.global.external_labels.cluster_id) (not .Values.prometheus.server.clusterIDConfigmap) }} + - name: CLUSTER_ID + value: {{ .Values.prometheus.server.global.external_labels.cluster_id }} + {{- end }} + {{- if .Values.prometheus.server.clusterIDConfigmap }} + - name: CLUSTER_ID + valueFrom: + configMapKeyRef: + name: {{ .Values.prometheus.server.clusterIDConfigmap }} + key: CLUSTER_ID + {{- end }} + {{- if .Values.kubecostModel.promClusterIDLabel }} + - name: PROM_CLUSTER_ID_LABEL + value: {{ .Values.kubecostModel.promClusterIDLabel }} + {{- end }} + - name: PV_ENABLED + value: {{ (quote .Values.persistentVolume.enabled) | default (quote true) }} + - name: RELEASE_NAME + value: {{ .Release.Name }} + - name: KUBECOST_NAMESPACE + value: {{ .Release.Namespace }} + - name: POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: KUBECOST_TOKEN + valueFrom: + configMapKeyRef: + name: {{ template "cost-analyzer.fullname" . }} + key: kubecost-token + {{- if .Values.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.kubecostMetrics.exporter.priorityClassName }} + priorityClassName: {{ .Values.kubecostMetrics.exporter.priorityClassName }} + {{- end }} + {{- with .Values.kubecostMetrics.exporter.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.kubecostMetrics.exporter.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.kubecostMetrics.exporter.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-metrics-service-monitor-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-metrics-service-monitor-template.yaml new file mode 100644 index 000000000..f858b77a3 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-metrics-service-monitor-template.yaml @@ -0,0 +1,41 @@ +{{- if .Values.kubecostMetrics }} +{{- if .Values.kubecostMetrics.exporter }} +{{- if .Values.kubecostMetrics.exporter.enabled }} +{{- if .Values.kubecostMetrics.exporter.serviceMonitor }} +{{- if .Values.kubecostMetrics.exporter.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "kubecost.kubeMetricsName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if .Values.kubecostMetrics.exporter.serviceMonitor.additionalLabels }} + {{ toYaml .Values.kubecostMetrics.exporter.serviceMonitor.additionalLabels | nindent 4 }} + {{- end }} +spec: + endpoints: + - port: tcp-metrics + honorLabels: true + interval: 1m + scrapeTimeout: 10s + path: /metrics + scheme: http + {{- with .Values.kubecostMetrics.exporter.serviceMonitor.metricRelabelings }} + metricRelabelings: {{ toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.kubecostMetrics.exporter.serviceMonitor.relabelings }} + relabelings: {{ toYaml . | nindent 8 }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + app: {{ include "kubecost.kubeMetricsName" . }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} + diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-metrics-service-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-metrics-service-template.yaml new file mode 100644 index 000000000..80ef198f8 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-metrics-service-template.yaml @@ -0,0 +1,34 @@ +{{- if .Values.kubecostMetrics }} +{{- if .Values.kubecostMetrics.exporter }} +{{- if .Values.kubecostMetrics.exporter.enabled }} +{{- $prometheusScrape := ternary .Values.kubecostMetrics.exporter.prometheusScrape true (kindIs "bool" .Values.kubecostMetrics.exporter.prometheusScrape) }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "kubecost.kubeMetricsName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ unset (include "cost-analyzer.commonLabels" . | fromYaml) "app" | toYaml | nindent 4 }} + app: {{ template "kubecost.kubeMetricsName" . }} +{{- if (or .Values.kubecostMetrics.exporter.service.annotations $prometheusScrape) }} + annotations: +{{- if .Values.kubecostMetrics.exporter.service.annotations }} +{{ toYaml .Values.kubecostMetrics.exporter.service.annotations | indent 4 }} +{{- end }} +{{- if $prometheusScrape }} + prometheus.io/scrape: "true" + prometheus.io/port: {{ (quote .Values.kubecostMetrics.exporter.port) | default (quote 9005) }} +{{- end }} +{{- end }} +spec: + ports: + - name: tcp-metrics + port: {{ .Values.kubecostMetrics.exporter.port | default 9005 }} + protocol: TCP + targetPort: {{ .Values.kubecostMetrics.exporter.port | default 9005 }} + selector: + app: {{ template "kubecost.kubeMetricsName" . }} + type: ClusterIP +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-oidc-secret-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-oidc-secret-template.yaml new file mode 100644 index 000000000..381514512 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-oidc-secret-template.yaml @@ -0,0 +1,16 @@ +{{- if .Values.oidc }} +{{- if and (not .Values.oidc.existingCustomSecret.enabled) .Values.oidc.secretName }} +{{- if .Values.oidc.clientSecret }} +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: {{ .Values.oidc.secretName }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +stringData: + clientSecret: {{ .Values.oidc.clientSecret }} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-priority-class-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-priority-class-template.yaml new file mode 100644 index 000000000..7a176d72a --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-priority-class-template.yaml @@ -0,0 +1,15 @@ +{{- if .Values.priority }} +{{- if .Values.priority.enabled }} +{{- if eq (len .Values.priority.name) 0 }} +apiVersion: scheduling.k8s.io/v1 +kind: PriorityClass +metadata: + name: {{ template "cost-analyzer.fullname" . }}-priority + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +value: {{ .Values.priority.value | default "1000000" }} +globalDefault: false +description: "Priority class for scheduling the cost-analyzer pod" +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-saml-secret-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-saml-secret-template.yaml new file mode 100644 index 000000000..e9a323057 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/kubecost-saml-secret-template.yaml @@ -0,0 +1,12 @@ +{{- if .Values.saml.enabled }} +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: {{ .Values.saml.authSecretName | default "kubecost-saml-secret" }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +stringData: + clientSecret: {{ .Values.saml.authSecret | default (randAlphaNum 32 | quote) }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/mimir-proxy-configmap-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/mimir-proxy-configmap-template.yaml new file mode 100644 index 000000000..08b93ee84 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/mimir-proxy-configmap-template.yaml @@ -0,0 +1,21 @@ +{{- if .Values.global.mimirProxy }} +{{- if .Values.global.mimirProxy.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "cost-analyzer.fullname" . }}-mimir-proxy + namespace: {{ .Release.Namespace }} +data: + default.conf: | + server { + listen {{ .Values.global.mimirProxy.port }}; + location / { + proxy_pass {{ .Values.global.mimirProxy.mimirEndpoint }}; + proxy_set_header X-Scope-OrgID "{{ .Values.global.mimirProxy.orgIdentifier }}"; + {{- if .Values.global.mimirProxy.basicAuth }} + proxy_set_header Authorization "Basic {{ (printf "%s:%s" .Values.global.mimirProxy.basicAuth.username .Values.global.mimirProxy.basicAuth.password) | b64enc }}"; + {{- end }} + } + } +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/mimir-proxy-deployment-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/mimir-proxy-deployment-template.yaml new file mode 100644 index 000000000..cbe8519b4 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/mimir-proxy-deployment-template.yaml @@ -0,0 +1,46 @@ +{{- if .Values.global.mimirProxy }} +{{- if .Values.global.mimirProxy.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "cost-analyzer.fullname" . }}-mimir-proxy + namespace: {{ .Release.Namespace }} + labels: + app: mimir-proxy + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: 1 + selector: + matchLabels: + app: mimir-proxy + template: + metadata: + labels: + app: mimir-proxy + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + containers: + - name: {{ .Values.global.mimirProxy.name }} + image: {{ .Values.global.mimirProxy.image }} + ports: + - containerPort: {{ .Values.global.mimirProxy.port }} + protocol: TCP + resources: {} + imagePullPolicy: IfNotPresent + volumeMounts: + - mountPath: /etc/nginx/conf.d + name: default-conf + readOnly: true + volumes: + - name: default-conf + configMap: + name: {{ template "cost-analyzer.fullname" . }}-mimir-proxy + items: + - key: default.conf + path: default.conf +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/mimir-proxy-service-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/mimir-proxy-service-template.yaml new file mode 100644 index 000000000..5e46b62f9 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/mimir-proxy-service-template.yaml @@ -0,0 +1,18 @@ +{{- if .Values.global.mimirProxy }} +{{- if .Values.global.mimirProxy.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "cost-analyzer.fullname" . }}-mimir-proxy + namespace: {{ .Release.Namespace }} +spec: + ports: + - name: mimir-proxy + protocol: TCP + port: {{ .Values.global.mimirProxy.port }} + targetPort: {{ .Values.global.mimirProxy.port }} + selector: + app: mimir-proxy + type: ClusterIP +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/model-ingress-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/model-ingress-template.yaml new file mode 100644 index 000000000..b55b2986c --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/model-ingress-template.yaml @@ -0,0 +1,51 @@ +{{- if .Values.kubecostModel.ingress -}} +{{- if .Values.kubecostModel.ingress.enabled -}} +{{- $fullName := include "cost-analyzer.fullname" . -}} +{{- $serviceName := include "cost-analyzer.serviceName" . -}} +{{- $ingressPaths := .Values.kubecostModel.ingress.paths -}} +{{- $ingressPathType := .Values.kubecostModel.ingress.pathType -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }}-model + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- with .Values.kubecostModel.ingress.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.kubecostModel.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: +{{- if .Values.kubecostModel.ingress.className }} + ingressClassName: {{ .Values.kubecostModel.ingress.className }} +{{- end }} +{{- if .Values.kubecostModel.ingress.tls }} + tls: + {{- range .Values.kubecostModel.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} + rules: + {{- range .Values.kubecostModel.ingress.hosts }} + - host: {{ . | quote }} + http: + paths: + {{- range $ingressPaths }} + - path: {{ . }} + pathType: {{ $ingressPathType }} + backend: + service: + name: {{ $serviceName }} + port: + name: tcp-model + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/network-costs-servicemonitor-template.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/network-costs-servicemonitor-template.yaml new file mode 100644 index 000000000..3cef9547d --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/network-costs-servicemonitor-template.yaml @@ -0,0 +1,32 @@ +{{- if .Values.serviceMonitor.networkCosts.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "cost-analyzer.networkCostsName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if .Values.serviceMonitor.networkCosts.additionalLabels }} + {{ toYaml .Values.serviceMonitor.networkCosts.additionalLabels | nindent 4 }} + {{- end }} +spec: + endpoints: + - port: metrics + honorLabels: true + interval: {{ .Values.serviceMonitor.networkCosts.interval }} + scrapeTimeout: {{ .Values.serviceMonitor.networkCosts.scrapeTimeout }} + path: /metrics + scheme: http + {{- with .Values.serviceMonitor.networkCosts.metricRelabelings }} + metricRelabelings: {{ toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.serviceMonitor.networkCosts.relabelings }} + relabelings: {{ toYaml . | nindent 8 }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + app: {{ include "cost-analyzer.networkCostsName" . }} +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/plugins-config.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/plugins-config.yaml new file mode 100644 index 000000000..bd939ac1e --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/plugins-config.yaml @@ -0,0 +1,13 @@ +{{- if and (not .Values.kubecostModel.plugins.existingCustomSecret.enabled) .Values.kubecostModel.plugins.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.kubecostModel.plugins.secretName }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + {{- range $key, $config := .Values.kubecostModel.plugins.configs }} + {{ $key }}_config.json: + {{ $config | b64enc | indent 4}} + {{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-configmap.yaml new file mode 100644 index 000000000..8f5b8315f --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-configmap.yaml @@ -0,0 +1,21 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.alertmanager.enabled (and (empty .Values.prometheus.alertmanager.configMapOverrideName) (empty .Values.prometheus.alertmanager.configFromSecret)) -}} +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} + name: {{ template "prometheus.alertmanager.fullname" . }} + namespace: {{ .Release.Namespace }} +data: +{{- $root := . -}} +{{- range $key, $value := .Values.prometheus.alertmanagerFiles }} + {{- if $key | regexMatch ".*\\.ya?ml$" }} + {{ $key }}: | +{{ toYaml $value | default "{}" | indent 4 }} + {{- else }} + {{ $key }}: {{ toYaml $value | indent 4 }} + {{- end }} +{{- end -}} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-deployment.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-deployment.yaml new file mode 100644 index 000000000..b3af15532 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-deployment.yaml @@ -0,0 +1,148 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.alertmanager.enabled (not .Values.prometheus.alertmanager.statefulSet.enabled) -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ template "prometheus.alertmanager.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + {{- include "prometheus.alertmanager.matchLabels" . | nindent 6 }} + replicas: {{ .Values.prometheus.alertmanager.replicaCount }} + {{- if .Values.prometheus.alertmanager.strategy }} + strategy: +{{ toYaml .Values.prometheus.alertmanager.strategy | indent 4 }} + {{- end }} + template: + metadata: + {{- if .Values.prometheus.alertmanager.podAnnotations }} + annotations: +{{ toYaml .Values.prometheus.alertmanager.podAnnotations | indent 8 }} + {{- end }} + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 8 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.alertmanager.podLabels}} + {{ toYaml .Values.prometheus.alertmanager.podLabels | nindent 8 }} + {{- end}} + spec: +{{- if .Values.prometheus.alertmanager.schedulerName }} + schedulerName: "{{ .Values.prometheus.alertmanager.schedulerName }}" +{{- end }} + serviceAccountName: {{ template "prometheus.serviceAccountName.alertmanager" . }} +{{- if .Values.prometheus.alertmanager.priorityClassName }} + priorityClassName: "{{ .Values.prometheus.alertmanager.priorityClassName }}" +{{- end }} + containers: + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.alertmanager.name }} + image: "{{ .Values.prometheus.alertmanager.image.repository }}:{{ .Values.prometheus.alertmanager.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.alertmanager.image.pullPolicy }}" + env: + {{- range $key, $value := .Values.prometheus.alertmanager.extraEnv }} + - name: {{ $key }} + value: {{ $value }} + {{- end }} + - name: POD_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP + args: + - --config.file=/etc/config/{{ .Values.prometheus.alertmanager.configFileName }} + - --storage.path={{ .Values.prometheus.alertmanager.persistentVolume.mountPath }} + - --cluster.advertise-address=$(POD_IP):6783 + {{- range $key, $value := .Values.prometheus.alertmanager.extraArgs }} + - --{{ $key }}={{ $value }} + {{- end }} + {{- if .Values.prometheus.alertmanager.baseURL }} + - --web.external-url={{ .Values.prometheus.alertmanager.baseURL }} + {{- end }} + + ports: + - containerPort: 9093 + readinessProbe: + httpGet: + path: {{ .Values.prometheus.alertmanager.prefixURL }}/-/ready + port: 9093 + initialDelaySeconds: 30 + timeoutSeconds: 30 + resources: +{{ toYaml .Values.prometheus.alertmanager.resources | indent 12 }} + volumeMounts: + - name: config-volume + mountPath: /etc/config + - name: storage-volume + mountPath: "{{ .Values.prometheus.alertmanager.persistentVolume.mountPath }}" + subPath: "{{ .Values.prometheus.alertmanager.persistentVolume.subPath }}" + {{- range .Values.prometheus.alertmanager.extraSecretMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + + {{- if .Values.prometheus.configmapReload.alertmanager.enabled }} + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.alertmanager.name }}-{{ .Values.prometheus.configmapReload.alertmanager.name }} + image: "{{ .Values.prometheus.configmapReload.alertmanager.image.repository }}:{{ .Values.prometheus.configmapReload.alertmanager.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.configmapReload.alertmanager.image.pullPolicy }}" + args: + - --watched-dir=/etc/config + - --reload-url=http://127.0.0.1:9093{{ .Values.prometheus.alertmanager.prefixURL }}/-/reload + resources: +{{ toYaml .Values.prometheus.configmapReload.alertmanager.resources | indent 12 }} + volumeMounts: + - name: config-volume + mountPath: /etc/config + readOnly: true + {{- end }} + {{- if .Values.prometheus.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.prometheus.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.prometheus.alertmanager.nodeSelector }} + nodeSelector: +{{ toYaml .Values.prometheus.alertmanager.nodeSelector | indent 8 }} + {{- end }} + {{- if .Values.prometheus.alertmanager.securityContext }} + securityContext: +{{ toYaml .Values.prometheus.alertmanager.securityContext | indent 8 }} + {{- end }} + {{- if .Values.prometheus.alertmanager.tolerations }} + tolerations: +{{ toYaml .Values.prometheus.alertmanager.tolerations | indent 8 }} + {{- end }} + {{- if .Values.prometheus.alertmanager.affinity }} + affinity: +{{ toYaml .Values.prometheus.alertmanager.affinity | indent 8 }} + {{- end }} + volumes: + - name: config-volume + {{- if empty .Values.prometheus.alertmanager.configFromSecret }} + configMap: + name: {{ if .Values.prometheus.alertmanager.configMapOverrideName }}{{ .Release.Name }}-{{ .Values.prometheus.alertmanager.configMapOverrideName }}{{- else }}{{ template "prometheus.alertmanager.fullname" . }}{{- end }} + {{- else }} + secret: + secretName: {{ .Values.prometheus.alertmanager.configFromSecret }} + {{- end }} + {{- range .Values.prometheus.alertmanager.extraSecretMounts }} + - name: {{ .name }} + secret: + secretName: {{ .secretName }} + {{- end }} + - name: storage-volume + {{- if .Values.prometheus.alertmanager.persistentVolume.enabled }} + persistentVolumeClaim: + claimName: {{ if .Values.prometheus.alertmanager.persistentVolume.existingClaim }}{{ .Values.prometheus.alertmanager.persistentVolume.existingClaim }}{{- else }}{{ template "prometheus.alertmanager.fullname" . }}{{- end }} + {{- else }} + emptyDir: {} + {{- end -}} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-ingress.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-ingress.yaml new file mode 100644 index 000000000..41757e0e1 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-ingress.yaml @@ -0,0 +1,41 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.alertmanager.enabled .Values.prometheus.alertmanager.ingress.enabled -}} +{{- $releaseName := .Release.Name -}} +{{- $serviceName := include "prometheus.alertmanager.fullname" . }} +{{- $servicePort := .Values.prometheus.alertmanager.service.servicePort -}} +{{- $extraPaths := .Values.prometheus.alertmanager.ingress.extraPaths -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: +{{- if .Values.prometheus.alertmanager.ingress.annotations }} + annotations: +{{ toYaml .Values.prometheus.alertmanager.ingress.annotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} +{{- range $key, $value := .Values.prometheus.alertmanager.ingress.extraLabels }} + {{ $key }}: {{ $value }} +{{- end }} + name: {{ template "prometheus.alertmanager.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + rules: + {{- range .Values.prometheus.alertmanager.ingress.hosts }} + {{- $url := splitList "/" . }} + - host: {{ first $url }} + http: + paths: +{{ if $extraPaths }} +{{ toYaml $extraPaths | indent 10 }} +{{- end }} + - path: /{{ rest $url | join "/" }} + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} + {{- end -}} +{{- if .Values.prometheus.alertmanager.ingress.tls }} + tls: +{{ toYaml .Values.prometheus.alertmanager.ingress.tls | indent 4 }} + {{- end -}} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-networkpolicy.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-networkpolicy.yaml new file mode 100644 index 000000000..c24a76ae7 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-networkpolicy.yaml @@ -0,0 +1,22 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.alertmanager.enabled .Values.prometheus.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ template "prometheus.alertmanager.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "prometheus.alertmanager.matchLabels" . | nindent 6 }} + ingress: + - from: + - podSelector: + matchLabels: + {{- include "prometheus.server.matchLabels" . | nindent 12 }} + - ports: + - port: 9093 +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-pdb.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-pdb.yaml new file mode 100644 index 000000000..123d24ee0 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-pdb.yaml @@ -0,0 +1,16 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.alertmanager.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ template "prometheus.alertmanager.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} +spec: + maxUnavailable: {{ .Values.prometheus.alertmanager.podDisruptionBudget.maxUnavailable }} + selector: + matchLabels: + {{- include "prometheus.alertmanager.labels" . | nindent 6 }} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-pvc.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-pvc.yaml new file mode 100644 index 000000000..dea65e5e5 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-pvc.yaml @@ -0,0 +1,35 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if not .Values.prometheus.alertmanager.statefulSet.enabled -}} +{{- if and .Values.prometheus.alertmanager.enabled .Values.prometheus.alertmanager.persistentVolume.enabled -}} +{{- if not .Values.prometheus.alertmanager.persistentVolume.existingClaim -}} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + {{- if .Values.prometheus.alertmanager.persistentVolume.annotations }} + annotations: +{{ toYaml .Values.prometheus.alertmanager.persistentVolume.annotations | indent 4 }} + {{- end }} + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} + name: {{ template "prometheus.alertmanager.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + accessModes: +{{ toYaml .Values.prometheus.alertmanager.persistentVolume.accessModes | indent 4 }} +{{- if .Values.prometheus.alertmanager.persistentVolume.storageClass }} +{{- if (eq "-" .Values.prometheus.alertmanager.persistentVolume.storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: "{{ .Values.prometheus.alertmanager.persistentVolume.storageClass }}" +{{- end }} +{{- end }} +{{- if .Values.prometheus.alertmanager.persistentVolume.volumeBindingMode }} + volumeBindingModeName: "{{ .Values.prometheus.alertmanager.persistentVolume.volumeBindingMode }}" +{{- end }} + resources: + requests: + storage: "{{ .Values.prometheus.alertmanager.persistentVolume.size }}" +{{- end -}} +{{- end -}} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-service-headless.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-service-headless.yaml new file mode 100644 index 000000000..2f68f4126 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-service-headless.yaml @@ -0,0 +1,33 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.alertmanager.enabled .Values.prometheus.alertmanager.statefulSet.enabled -}} +apiVersion: v1 +kind: Service +metadata: +{{- if .Values.prometheus.alertmanager.statefulSet.headless.annotations }} + annotations: +{{ toYaml .Values.prometheus.alertmanager.statefulSet.headless.annotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} +{{- if .Values.prometheus.alertmanager.statefulSet.headless.labels }} +{{ toYaml .Values.prometheus.alertmanager.statefulSet.headless.labels | indent 4 }} +{{- end }} + name: {{ template "prometheus.alertmanager.fullname" . }}-headless + namespace: {{ .Release.Namespace }} +spec: + clusterIP: None + ports: + - name: http + port: {{ .Values.prometheus.alertmanager.statefulSet.headless.servicePort }} + protocol: TCP + targetPort: 9093 +{{- if .Values.prometheus.alertmanager.statefulSet.headless.enableMeshPeer }} + - name: meshpeer + port: 6783 + protocol: TCP + targetPort: 6783 +{{- end }} + selector: + {{- include "prometheus.alertmanager.matchLabels" . | nindent 4 }} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-service.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-service.yaml new file mode 100644 index 000000000..838d39ba4 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-service.yaml @@ -0,0 +1,55 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.alertmanager.enabled -}} +apiVersion: v1 +kind: Service +metadata: +{{- if .Values.prometheus.alertmanager.service.annotations }} + annotations: +{{ toYaml .Values.prometheus.alertmanager.service.annotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} +{{- if .Values.prometheus.alertmanager.service.labels }} +{{ toYaml .Values.prometheus.alertmanager.service.labels | indent 4 }} +{{- end }} + name: {{ template "prometheus.alertmanager.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: +{{- if .Values.prometheus.alertmanager.service.clusterIP }} + clusterIP: {{ .Values.prometheus.alertmanager.service.clusterIP }} +{{- end }} +{{- if .Values.prometheus.alertmanager.service.externalIPs }} + externalIPs: +{{ toYaml .Values.prometheus.alertmanager.service.externalIPs | indent 4 }} +{{- end }} +{{- if .Values.prometheus.alertmanager.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.prometheus.alertmanager.service.loadBalancerIP }} +{{- end }} +{{- if .Values.prometheus.alertmanager.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- range $cidr := .Values.prometheus.alertmanager.service.loadBalancerSourceRanges }} + - {{ $cidr }} + {{- end }} +{{- end }} + ports: + - name: http + port: {{ .Values.prometheus.alertmanager.service.servicePort }} + protocol: TCP + targetPort: 9093 + {{- if .Values.prometheus.alertmanager.service.nodePort }} + nodePort: {{ .Values.prometheus.alertmanager.service.nodePort }} + {{- end }} +{{- if .Values.prometheus.alertmanager.service.enableMeshPeer }} + - name: meshpeer + port: 6783 + protocol: TCP + targetPort: 6783 +{{- end }} + selector: + {{- include "prometheus.alertmanager.matchLabels" . | nindent 4 }} +{{- if .Values.prometheus.alertmanager.service.sessionAffinity }} + sessionAffinity: {{ .Values.prometheus.alertmanager.service.sessionAffinity }} +{{- end }} + type: "{{ .Values.prometheus.alertmanager.service.type }}" +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-serviceaccount.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-serviceaccount.yaml new file mode 100644 index 000000000..99257bbf8 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-serviceaccount.yaml @@ -0,0 +1,11 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.alertmanager.enabled .Values.prometheus.serviceAccounts.alertmanager.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} + name: {{ template "prometheus.serviceAccountName.alertmanager" . }} + namespace: {{ .Release.Namespace }} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-statefulset.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-statefulset.yaml new file mode 100644 index 000000000..26e05f1fb --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-alertmanager-statefulset.yaml @@ -0,0 +1,155 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.alertmanager.enabled .Values.prometheus.alertmanager.statefulSet.enabled -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} + name: {{ template "prometheus.alertmanager.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + serviceName: {{ template "prometheus.alertmanager.fullname" . }}-headless + selector: + matchLabels: + {{- include "prometheus.alertmanager.matchLabels" . | nindent 6 }} + replicas: {{ .Values.prometheus.alertmanager.replicaCount }} + podManagementPolicy: {{ .Values.prometheus.alertmanager.statefulSet.podManagementPolicy }} + template: + metadata: + {{- if .Values.prometheus.alertmanager.podAnnotations }} + annotations: +{{ toYaml .Values.prometheus.alertmanager.podAnnotations | indent 8 }} + {{- end }} + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 8 }} + spec: +{{- if .Values.prometheus.alertmanager.affinity }} + affinity: +{{ toYaml .Values.prometheus.alertmanager.affinity | indent 8 }} +{{- end }} +{{- if .Values.prometheus.alertmanager.schedulerName }} + schedulerName: "{{ .Values.prometheus.alertmanager.schedulerName }}" +{{- end }} + serviceAccountName: {{ template "prometheus.serviceAccountName.alertmanager" . }} +{{- if .Values.prometheus.alertmanager.priorityClassName }} + priorityClassName: "{{ .Values.prometheus.alertmanager.priorityClassName }}" +{{- end }} + containers: + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.alertmanager.name }} + image: "{{ .Values.prometheus.alertmanager.image.repository }}:{{ .Values.prometheus.alertmanager.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.alertmanager.image.pullPolicy }}" + env: + {{- range $key, $value := .Values.prometheus.alertmanager.extraEnv }} + - name: {{ $key }} + value: {{ $value }} + {{- end }} + - name: POD_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP + args: + - --config.file=/etc/config/alertmanager.yml + - --storage.path={{ .Values.prometheus.alertmanager.persistentVolume.mountPath }} + - --cluster.advertise-address=$(POD_IP):6783 + {{- if .Values.prometheus.alertmanager.statefulSet.headless.enableMeshPeer }} + - --cluster.listen-address=0.0.0.0:6783 + {{- range $n := until (.Values.prometheus.alertmanager.replicaCount | int) }} + - --cluster.peer={{ template "prometheus.alertmanager.fullname" $ }}-{{ $n }}.{{ template "prometheus.alertmanager.fullname" $ }}-headless:6783 + {{- end }} + {{- end }} + {{- range $key, $value := .Values.prometheus.alertmanager.extraArgs }} + - --{{ $key }}={{ $value }} + {{- end }} + {{- if .Values.prometheus.alertmanager.baseURL }} + - --web.external-url={{ .Values.prometheus.alertmanager.baseURL }} + {{- end }} + + ports: + - containerPort: 9093 + readinessProbe: + httpGet: + path: {{ .Values.prometheus.alertmanager.prefixURL }}/#/status + port: 9093 + initialDelaySeconds: 30 + timeoutSeconds: 30 + resources: +{{ toYaml .Values.prometheus.alertmanager.resources | indent 12 }} + volumeMounts: + - name: config-volume + mountPath: /etc/config + - name: storage-volume + mountPath: "{{ .Values.prometheus.alertmanager.persistentVolume.mountPath }}" + subPath: "{{ .Values.prometheus.alertmanager.persistentVolume.subPath }}" + {{- range .Values.prometheus.alertmanager.extraSecretMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- if .Values.prometheus.configmapReload.alertmanager.enabled }} + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.alertmanager.name }}-{{ .Values.prometheus.configmapReload.alertmanager.name }} + image: "{{ .Values.prometheus.configmapReload.alertmanager.image.repository }}:{{ .Values.prometheus.configmapReload.alertmanager.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.configmapReload.alertmanager.image.pullPolicy }}" + args: + - --watched-dir=/etc/config + - --reload-url=http://localhost:9093{{ .Values.prometheus.alertmanager.prefixURL }}/-/reload + resources: +{{ toYaml .Values.prometheus.configmapReload.alertmanager.resources | indent 12 }} + volumeMounts: + - name: config-volume + mountPath: /etc/config + readOnly: true + {{- end }} + {{- if .Values.prometheus.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.prometheus.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.prometheus.alertmanager.nodeSelector }} + nodeSelector: +{{ toYaml .Values.prometheus.alertmanager.nodeSelector | indent 8 }} + {{- end }} + {{- if .Values.prometheus.alertmanager.securityContext }} + securityContext: +{{ toYaml .Values.prometheus.alertmanager.securityContext | indent 8 }} + {{- end }} + {{- if .Values.prometheus.alertmanager.tolerations }} + tolerations: +{{ toYaml .Values.prometheus.alertmanager.tolerations | indent 8 }} + {{- end }} + volumes: + - name: config-volume + configMap: + name: {{ if .Values.prometheus.alertmanager.configMapOverrideName }}{{ .Release.Name }}-{{ .Values.prometheus.alertmanager.configMapOverrideName }}{{- else }}{{ template "prometheus.alertmanager.fullname" . }}{{- end }} + {{- range .Values.prometheus.alertmanager.extraSecretMounts }} + - name: {{ .name }} + secret: + secretName: {{ .secretName }} + {{- end }} +{{- if .Values.prometheus.alertmanager.persistentVolume.enabled }} + volumeClaimTemplates: + - metadata: + name: storage-volume + {{- if .Values.prometheus.alertmanager.persistentVolume.annotations }} + annotations: +{{ toYaml .Values.prometheus.alertmanager.persistentVolume.annotations | indent 10 }} + {{- end }} + spec: + accessModes: +{{ toYaml .Values.prometheus.alertmanager.persistentVolume.accessModes | indent 10 }} + resources: + requests: + storage: "{{ .Values.prometheus.alertmanager.persistentVolume.size }}" + {{- if .Values.prometheus.server.persistentVolume.storageClass }} + {{- if (eq "-" .Values.prometheus.server.persistentVolume.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ .Values.prometheus.alertmanager.persistentVolume.storageClass }}" + {{- end }} + {{- end }} +{{- else }} + - name: storage-volume + emptyDir: {} +{{- end }} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-node-exporter-daemonset.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-node-exporter-daemonset.yaml new file mode 100644 index 000000000..3529d6bdd --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-node-exporter-daemonset.yaml @@ -0,0 +1,139 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.nodeExporter.enabled -}} +apiVersion: apps/v1 +kind: DaemonSet +metadata: +{{- if .Values.prometheus.nodeExporter.deploymentAnnotations }} + annotations: +{{ toYaml .Values.prometheus.nodeExporter.deploymentAnnotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.nodeExporter.labels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ template "prometheus.nodeExporter.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + {{- include "prometheus.nodeExporter.matchLabels" . | nindent 6 }} + {{- if .Values.prometheus.nodeExporter.updateStrategy }} + updateStrategy: +{{ toYaml .Values.prometheus.nodeExporter.updateStrategy | indent 4 }} + {{- end }} + template: + metadata: + {{- if .Values.prometheus.nodeExporter.podAnnotations }} + annotations: +{{ toYaml .Values.prometheus.nodeExporter.podAnnotations | indent 8 }} + {{- end }} + labels: + {{- include "prometheus.nodeExporter.labels" . | nindent 8 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} +{{- if .Values.prometheus.nodeExporter.pod.labels }} +{{ toYaml .Values.prometheus.nodeExporter.pod.labels | indent 8 }} +{{- end }} + spec: +{{- if .Values.prometheus.nodeExporter.affinity }} + affinity: +{{ toYaml .Values.prometheus.nodeExporter.affinity | indent 8 }} +{{- end }} + serviceAccountName: {{ template "prometheus.serviceAccountName.nodeExporter" . }} +{{- if .Values.prometheus.nodeExporter.dnsPolicy }} + dnsPolicy: "{{ .Values.prometheus.nodeExporter.dnsPolicy }}" +{{- end }} +{{- if .Values.prometheus.nodeExporter.priorityClassName }} + priorityClassName: "{{ .Values.prometheus.nodeExporter.priorityClassName }}" +{{- end }} + containers: + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.nodeExporter.name }} + image: "{{ .Values.prometheus.nodeExporter.image.repository }}:{{ .Values.prometheus.nodeExporter.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.nodeExporter.image.pullPolicy }}" + args: + - --path.procfs=/host/proc + - --path.sysfs=/host/sys + {{- if .Values.prometheus.nodeExporter.hostNetwork }} + - --web.listen-address=:{{ .Values.prometheus.nodeExporter.service.hostPort }} + {{- end }} + {{- range $key, $value := .Values.prometheus.nodeExporter.extraArgs }} + {{- if $value }} + - --{{ $key }}={{ $value }} + {{- else }} + - --{{ $key }} + {{- end }} + {{- end }} + ports: + - name: metrics + {{- if .Values.prometheus.nodeExporter.hostNetwork }} + containerPort: {{ .Values.prometheus.nodeExporter.service.hostPort }} + {{- else }} + containerPort: 9100 + {{- end }} + hostPort: {{ .Values.prometheus.nodeExporter.service.hostPort }} + resources: +{{ toYaml .Values.prometheus.nodeExporter.resources | indent 12 }} + volumeMounts: + - name: proc + mountPath: /host/proc + readOnly: true + - name: sys + mountPath: /host/sys + readOnly: true + {{- range .Values.prometheus.nodeExporter.extraHostPathMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + readOnly: {{ .readOnly }} + {{- if .mountPropagation }} + mountPropagation: {{ .mountPropagation }} + {{- end }} + {{- end }} + {{- range .Values.prometheus.nodeExporter.extraConfigmapMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- if .Values.prometheus.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.prometheus.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.prometheus.nodeExporter.hostNetwork }} + hostNetwork: true + {{- end }} + {{- if .Values.prometheus.nodeExporter.hostPID }} + hostPID: true + {{- end }} + {{- if .Values.prometheus.nodeExporter.tolerations }} + tolerations: +{{ toYaml .Values.prometheus.nodeExporter.tolerations | indent 8 }} + {{- end }} + {{- if .Values.prometheus.nodeExporter.nodeSelector }} + nodeSelector: +{{ toYaml .Values.prometheus.nodeExporter.nodeSelector | indent 8 }} + {{- end }} + {{- if .Values.prometheus.nodeExporter.securityContext }} + securityContext: +{{ toYaml .Values.prometheus.nodeExporter.securityContext | indent 8 }} + {{- end }} + volumes: + - name: proc + hostPath: + path: /proc + - name: sys + hostPath: + path: /sys + {{- range .Values.prometheus.nodeExporter.extraHostPathMounts }} + - name: {{ .name }} + hostPath: + path: {{ .hostPath }} + {{- end }} + {{- range .Values.prometheus.nodeExporter.extraConfigmapMounts }} + - name: {{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} + +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-node-exporter-ocp-scc.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-node-exporter-ocp-scc.yaml new file mode 100644 index 000000000..e226f9bea --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-node-exporter-ocp-scc.yaml @@ -0,0 +1,29 @@ +{{- if and (.Capabilities.APIVersions.Has "security.openshift.io/v1/SecurityContextConstraints") (.Values.global.platforms.openshift.scc.nodeExporter) (.Values.prometheus.nodeExporter.enabled) }} +apiVersion: security.openshift.io/v1 +kind: SecurityContextConstraints +metadata: + name: {{ template "prometheus.nodeExporter.fullname" . }} +priority: 10 +allowPrivilegedContainer: true +allowHostDirVolumePlugin: true +allowHostNetwork: true +allowHostPorts: true +allowHostPID: true +allowHostIPC: false +readOnlyRootFilesystem: false +runAsUser: + type: RunAsAny +fsGroup: + type: RunAsAny +seLinuxContext: + type: RunAsAny +supplementalGroups: + type: RunAsAny +seccompProfiles: +- runtime/default +volumes: + - hostPath + - projected +users: + - system:serviceaccount:{{ .Release.Namespace }}:{{ template "prometheus.serviceAccountName.nodeExporter" . }} +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-node-exporter-service.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-node-exporter-service.yaml new file mode 100644 index 000000000..9b8167e8d --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-node-exporter-service.yaml @@ -0,0 +1,47 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.nodeExporter.enabled -}} +apiVersion: v1 +kind: Service +metadata: +{{- if .Values.prometheus.nodeExporter.service.annotations }} + annotations: +{{ toYaml .Values.prometheus.nodeExporter.service.annotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.nodeExporter.labels" . | nindent 4 }} +{{- if .Values.prometheus.nodeExporter.service.labels }} +{{ toYaml .Values.prometheus.nodeExporter.service.labels | indent 4 }} +{{- end }} + name: {{ template "prometheus.nodeExporter.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: +{{- if .Values.prometheus.nodeExporter.service.clusterIP }} + clusterIP: {{ .Values.prometheus.nodeExporter.service.clusterIP }} +{{- end }} +{{- if .Values.prometheus.nodeExporter.service.externalIPs }} + externalIPs: +{{ toYaml .Values.prometheus.nodeExporter.service.externalIPs | indent 4 }} +{{- end }} +{{- if .Values.prometheus.nodeExporter.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.prometheus.nodeExporter.service.loadBalancerIP }} +{{- end }} +{{- if .Values.prometheus.nodeExporter.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- range $cidr := .Values.prometheus.nodeExporter.service.loadBalancerSourceRanges }} + - {{ $cidr }} + {{- end }} +{{- end }} + ports: + - name: tcp-metrics + port: {{ .Values.prometheus.nodeExporter.service.servicePort }} + protocol: TCP + {{- if .Values.prometheus.nodeExporter.hostNetwork }} + targetPort: {{ .Values.prometheus.nodeExporter.service.hostPort }} + {{- else }} + targetPort: 9100 + {{- end }} + selector: + {{- include "prometheus.nodeExporter.matchLabels" . | nindent 4 }} + type: "{{ .Values.prometheus.nodeExporter.service.type }}" +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-node-exporter-serviceaccount.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-node-exporter-serviceaccount.yaml new file mode 100644 index 000000000..3cb68d8e4 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-node-exporter-serviceaccount.yaml @@ -0,0 +1,11 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.nodeExporter.enabled .Values.prometheus.serviceAccounts.nodeExporter.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + {{- include "prometheus.nodeExporter.labels" . | nindent 4 }} + name: {{ template "prometheus.serviceAccountName.nodeExporter" . }} + namespace: {{ .Release.Namespace }} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-deployment.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-deployment.yaml new file mode 100644 index 000000000..072c028d1 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-deployment.yaml @@ -0,0 +1,106 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.pushgateway.enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + {{- include "prometheus.pushgateway.labels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ template "prometheus.pushgateway.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + selector: + {{- if .Values.prometheus.pushgateway.schedulerName }} + schedulerName: "{{ .Values.prometheus.pushgateway.schedulerName }}" + {{- end }} + matchLabels: + {{- include "prometheus.pushgateway.matchLabels" . | nindent 6 }} + replicas: {{ .Values.prometheus.pushgateway.replicaCount }} + {{- if .Values.prometheus.pushgateway.strategy }} + strategy: +{{ toYaml .Values.prometheus.pushgateway.strategy | indent 4 }} + {{- end }} + template: + metadata: + {{- if .Values.prometheus.pushgateway.podAnnotations }} + annotations: +{{ toYaml .Values.prometheus.pushgateway.podAnnotations | indent 8 }} + {{- end }} + labels: + {{- include "prometheus.pushgateway.labels" . | nindent 8 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ template "prometheus.serviceAccountName.pushgateway" . }} +{{- if .Values.prometheus.pushgateway.priorityClassName }} + priorityClassName: "{{ .Values.prometheus.pushgateway.priorityClassName }}" +{{- end }} + containers: + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.pushgateway.name }} + image: "{{ .Values.prometheus.pushgateway.image.repository }}:{{ .Values.prometheus.pushgateway.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.pushgateway.image.pullPolicy }}" + args: + {{- range $key, $value := .Values.prometheus.pushgateway.extraArgs }} + - --{{ $key }}={{ $value }} + {{- end }} + ports: + - containerPort: 9091 + livenessProbe: + httpGet: + {{- if (index .Values.prometheus "pushgateway" "extraArgs" "web.route-prefix") }} + path: /{{ index .Values.prometheus "pushgateway" "extraArgs" "web.route-prefix" }}/-/healthy + {{- else }} + path: /-/healthy + {{- end }} + port: 9091 + initialDelaySeconds: 10 + timeoutSeconds: 10 + readinessProbe: + httpGet: + {{- if (index .Values.prometheus "pushgateway" "extraArgs" "web.route-prefix") }} + path: /{{ index .Values.prometheus "pushgateway" "extraArgs" "web.route-prefix" }}/-/ready + {{- else }} + path: /-/ready + {{- end }} + port: 9091 + initialDelaySeconds: 10 + timeoutSeconds: 10 + resources: +{{ toYaml .Values.prometheus.pushgateway.resources | indent 12 }} + {{- if .Values.prometheus.pushgateway.persistentVolume.enabled }} + volumeMounts: + - name: storage-volume + mountPath: "{{ .Values.prometheus.pushgateway.persistentVolume.mountPath }}" + subPath: "{{ .Values.prometheus.pushgateway.persistentVolume.subPath }}" + {{- end }} + {{- if .Values.prometheus.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.prometheus.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.prometheus.pushgateway.nodeSelector }} + nodeSelector: +{{ toYaml .Values.prometheus.pushgateway.nodeSelector | indent 8 }} + {{- end }} + {{- if .Values.prometheus.pushgateway.securityContext }} + securityContext: +{{ toYaml .Values.prometheus.pushgateway.securityContext | indent 8 }} + {{- end }} + {{- if .Values.prometheus.pushgateway.tolerations }} + tolerations: +{{ toYaml .Values.prometheus.pushgateway.tolerations | indent 8 }} + {{- end }} + {{- if .Values.prometheus.pushgateway.affinity }} + affinity: +{{ toYaml .Values.prometheus.pushgateway.affinity | indent 8 }} + {{- end }} + {{- if .Values.prometheus.pushgateway.persistentVolume.enabled }} + volumes: + - name: storage-volume + persistentVolumeClaim: + claimName: {{ if .Values.prometheus.pushgateway.persistentVolume.existingClaim }}{{ .Values.prometheus.pushgateway.persistentVolume.existingClaim }}{{- else }}{{ template "prometheus.pushgateway.fullname" . }}{{- end }} + {{- end -}} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-ingress.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-ingress.yaml new file mode 100644 index 000000000..2d3f1d283 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-ingress.yaml @@ -0,0 +1,38 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.pushgateway.enabled .Values.prometheus.pushgateway.ingress.enabled -}} +{{- $releaseName := .Release.Name -}} +{{- $serviceName := include "prometheus.pushgateway.fullname" . }} +{{- $servicePort := .Values.prometheus.pushgateway.service.servicePort -}} +{{- $extraPaths := .Values.prometheus.pushgateway.ingress.extraPaths -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: +{{- if .Values.prometheus.pushgateway.ingress.annotations }} + annotations: +{{ toYaml .Values.prometheus.pushgateway.ingress.annotations | indent 4}} +{{- end }} + labels: + {{- include "prometheus.pushgateway.labels" . | nindent 4 }} + name: {{ template "prometheus.pushgateway.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + rules: + {{- range .Values.prometheus.pushgateway.ingress.hosts }} + {{- $url := splitList "/" . }} + - host: {{ first $url }} + http: + paths: +{{ if $extraPaths }} +{{ toYaml $extraPaths | indent 10 }} +{{- end }} + - path: /{{ rest $url | join "/" }} + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} + {{- end -}} +{{- if .Values.prometheus.pushgateway.ingress.tls }} + tls: +{{ toYaml .Values.prometheus.pushgateway.ingress.tls | indent 4 }} + {{- end -}} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-networkpolicy.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-networkpolicy.yaml new file mode 100644 index 000000000..b6e41eedf --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-networkpolicy.yaml @@ -0,0 +1,22 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.pushgateway.enabled .Values.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ template "prometheus.pushgateway.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "prometheus.pushgateway.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "prometheus.pushgateway.matchLabels" . | nindent 6 }} + ingress: + - from: + - podSelector: + matchLabels: + {{- include "prometheus.server.matchLabels" . | nindent 12 }} + - ports: + - port: 9091 +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-pdb.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-pdb.yaml new file mode 100644 index 000000000..00f7e4502 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-pdb.yaml @@ -0,0 +1,15 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.pushgateway.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ template "prometheus.pushgateway.fullname" . }} + labels: + {{- include "prometheus.pushgateway.labels" . | nindent 4 }} +spec: + maxUnavailable: {{ .Values.prometheus.pushgateway.podDisruptionBudget.maxUnavailable }} + selector: + matchLabels: + {{- include "prometheus.pushgateway.labels" . | nindent 6 }} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-pvc.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-pvc.yaml new file mode 100644 index 000000000..ba22f5921 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-pvc.yaml @@ -0,0 +1,35 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.pushgateway.enabled -}} +{{- if .Values.prometheus.pushgateway.persistentVolume.enabled -}} +{{- if not .Values.prometheus.pushgateway.persistentVolume.existingClaim -}} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + {{- if .Values.prometheus.pushgateway.persistentVolume.annotations }} + annotations: +{{ toYaml .Values.prometheus.pushgateway.persistentVolume.annotations | indent 4 }} + {{- end }} + labels: + {{- include "prometheus.pushgateway.labels" . | nindent 4 }} + name: {{ template "prometheus.pushgateway.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + accessModes: +{{ toYaml .Values.prometheus.pushgateway.persistentVolume.accessModes | indent 4 }} +{{- if .Values.prometheus.pushgateway.persistentVolume.storageClass }} +{{- if (eq "-" .Values.prometheus.pushgateway.persistentVolume.storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: "{{ .Values.prometheus.pushgateway.persistentVolume.storageClass }}" +{{- end }} +{{- end }} +{{- if .Values.prometheus.pushgateway.persistentVolume.volumeBindingMode }} + volumeBindingModeName: "{{ .Values.prometheus.pushgateway.persistentVolume.volumeBindingMode }}" +{{- end }} + resources: + requests: + storage: "{{ .Values.prometheus.pushgateway.persistentVolume.size }}" +{{- end -}} +{{- end -}} +{{ end }} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-service.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-service.yaml new file mode 100644 index 000000000..3e8811704 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-service.yaml @@ -0,0 +1,43 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.pushgateway.enabled -}} +apiVersion: v1 +kind: Service +metadata: +{{- if .Values.prometheus.pushgateway.service.annotations }} + annotations: +{{ toYaml .Values.prometheus.pushgateway.service.annotations | indent 4}} +{{- end }} + labels: + {{- include "prometheus.pushgateway.labels" . | nindent 4 }} +{{- if .Values.prometheus.pushgateway.service.labels }} +{{ toYaml .Values.prometheus.pushgateway.service.labels | indent 4}} +{{- end }} + name: {{ template "prometheus.pushgateway.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: +{{- if .Values.prometheus.pushgateway.service.clusterIP }} + clusterIP: {{ .Values.prometheus.pushgateway.service.clusterIP }} +{{- end }} +{{- if .Values.prometheus.pushgateway.service.externalIPs }} + externalIPs: +{{ toYaml .Values.prometheus.pushgateway.service.externalIPs | indent 4 }} +{{- end }} +{{- if .Values.prometheus.pushgateway.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.prometheus.pushgateway.service.loadBalancerIP }} +{{- end }} +{{- if .Values.prometheus.pushgateway.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- range $cidr := .Values.prometheus.pushgateway.service.loadBalancerSourceRanges }} + - {{ $cidr }} + {{- end }} +{{- end }} + ports: + - name: http + port: {{ .Values.prometheus.pushgateway.service.servicePort }} + protocol: TCP + targetPort: 9091 + selector: + {{- include "prometheus.pushgateway.matchLabels" . | nindent 4 }} + type: "{{ .Values.prometheus.pushgateway.service.type }}" +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-serviceaccount.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-serviceaccount.yaml new file mode 100644 index 000000000..1339e4b6b --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-pushgateway-serviceaccount.yaml @@ -0,0 +1,11 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.pushgateway.enabled .Values.prometheus.serviceAccounts.pushgateway.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + {{- include "prometheus.pushgateway.labels" . | nindent 4 }} + name: {{ template "prometheus.serviceAccountName.pushgateway" . }} + namespace: {{ .Release.Namespace }} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-clusterrole.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-clusterrole.yaml new file mode 100644 index 000000000..367219555 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-clusterrole.yaml @@ -0,0 +1,39 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.server.enabled .Values.prometheus.rbac.create -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} + name: {{ template "prometheus.server.fullname" . }} +rules: + - apiGroups: + - "" + resources: + - nodes + - nodes/proxy + - nodes/metrics + - services + - endpoints + - pods + - ingresses + - configmaps + verbs: + - get + - list + - watch + - apiGroups: + - networking.k8s.io + resources: + - ingresses/status + - ingresses + verbs: + - get + - list + - watch + - nonResourceURLs: + - "/metrics" + verbs: + - get +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-clusterrolebinding.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-clusterrolebinding.yaml new file mode 100644 index 000000000..e03d8e443 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-clusterrolebinding.yaml @@ -0,0 +1,18 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.server.enabled .Values.prometheus.rbac.create -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} + name: {{ template "prometheus.server.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ template "prometheus.serviceAccountName.server" . }} + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "prometheus.server.fullname" . }} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-configmap.yaml new file mode 100644 index 000000000..ca91b2d4a --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-configmap.yaml @@ -0,0 +1,90 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.server.enabled -}} +{{- if (empty .Values.prometheus.server.configMapOverrideName) -}} +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} + name: {{ template "prometheus.server.fullname" . }} + namespace: {{ .Release.Namespace }} +data: +{{- $root := . -}} +{{- range $key, $value := .Values.prometheus.serverFiles }} + {{ $key }}: | +{{- if eq $key "prometheus.yml" }} + global: +{{ $root.Values.prometheus.server.global | toYaml | trimSuffix "\n" | indent 6 }} +{{- if $root.Values.global.amp.enabled }} + remote_write: + - url: {{ $root.Values.global.amp.remoteWriteService }} + sigv4: +{{ $root.Values.global.amp.sigv4 | toYaml | indent 8 }} +{{- end }} +{{- if $root.Values.prometheus.server.remoteWrite }} + remote_write: +{{ $root.Values.prometheus.server.remoteWrite | toYaml | indent 4 }} +{{- end }} +{{- if $root.Values.prometheus.server.remoteRead }} + remote_read: +{{ $root.Values.prometheus.server.remoteRead | toYaml | indent 4 }} +{{- end }} +{{- end }} +{{- if eq $key "alerts" }} +{{- if and (not (empty $value)) (empty $value.groups) }} + groups: +{{- range $ruleKey, $ruleValue := $value }} + - name: {{ $ruleKey -}}.rules + rules: +{{ $ruleValue | toYaml | trimSuffix "\n" | indent 6 }} +{{- end }} +{{- else }} +{{ toYaml $value | indent 4 }} +{{- end }} +{{- else }} +{{ toYaml $value | default "{}" | indent 4 }} +{{- end }} +{{- if eq $key "prometheus.yml" -}} +{{- if $root.Values.prometheus.extraScrapeConfigs }} +{{ tpl $root.Values.prometheus.extraScrapeConfigs $root | indent 4 }} +{{- end -}} +{{- if or ($root.Values.prometheus.alertmanager.enabled) ($root.Values.prometheus.server.alertmanagers) }} + alerting: +{{- if $root.Values.prometheus.alertRelabelConfigs }} +{{ $root.Values.prometheus.alertRelabelConfigs | toYaml | trimSuffix "\n" | indent 6 }} +{{- end }} + alertmanagers: +{{- if $root.Values.prometheus.server.alertmanagers }} +{{ toYaml $root.Values.prometheus.server.alertmanagers | indent 8 }} +{{- else }} + - kubernetes_sd_configs: + - role: pod + tls_config: + ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt + bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token + {{- if $root.Values.prometheus.alertmanager.prefixURL }} + path_prefix: {{ $root.Values.prometheus.alertmanager.prefixURL }} + {{- end }} + relabel_configs: + - source_labels: [__meta_kubernetes_namespace] + regex: {{ $root.Release.Namespace }} + action: keep + - source_labels: [__meta_kubernetes_pod_label_app] + regex: {{ template "prometheus.name" $root }} + action: keep + - source_labels: [__meta_kubernetes_pod_label_component] + regex: alertmanager + action: keep + - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_probe] + regex: {{ index $root.Values.prometheus.alertmanager.podAnnotations "prometheus.io/probe" | default ".*" }} + action: keep + - source_labels: [__meta_kubernetes_pod_container_port_number] + regex: + action: drop +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-deployment.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-deployment.yaml new file mode 100644 index 000000000..8f2d60d3e --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-deployment.yaml @@ -0,0 +1,265 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.server.enabled -}} +{{- if not .Values.prometheus.server.statefulSet.enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: +{{- if .Values.prometheus.server.deploymentAnnotations }} + annotations: +{{ toYaml .Values.prometheus.server.deploymentAnnotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ template "prometheus.server.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + {{- include "prometheus.server.matchLabels" . | nindent 6 }} + replicas: {{ .Values.prometheus.server.replicaCount }} + {{- if .Values.prometheus.server.strategy }} + strategy: +{{ toYaml .Values.prometheus.server.strategy | indent 4 }} + {{- end }} + template: + metadata: + {{- if .Values.prometheus.server.podAnnotations }} + annotations: +{{ toYaml .Values.prometheus.server.podAnnotations | indent 8 }} + {{- end }} + labels: + {{/* + Force pod restarts on upgrades to ensure the configmap is current + */}} + {{- if not .Values.global.platforms.cicd.enabled }} + helm-rollout-restarter: {{ randAlphaNum 5 | quote }} + {{- end }} + {{- include "prometheus.server.labels" . | nindent 8 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.server.podLabels}} + {{ toYaml .Values.prometheus.server.podLabels | nindent 8 }} + {{- end}} + spec: +{{- if .Values.prometheus.server.priorityClassName }} + priorityClassName: "{{ .Values.prometheus.server.priorityClassName }}" +{{- end }} +{{- if .Values.prometheus.server.schedulerName }} + schedulerName: "{{ .Values.prometheus.server.schedulerName }}" +{{- end }} + serviceAccountName: {{ template "prometheus.serviceAccountName.server" . }} + {{- if .Values.prometheus.server.extraInitContainers }} + initContainers: +{{ toYaml .Values.prometheus.server.extraInitContainers | indent 8 }} + {{- end }} + containers: + {{- if .Values.prometheus.configmapReload.prometheus.enabled }} + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.server.name }}-{{ .Values.prometheus.configmapReload.prometheus.name }} + image: "{{ .Values.prometheus.configmapReload.prometheus.image.repository }}:{{ .Values.prometheus.configmapReload.prometheus.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.configmapReload.prometheus.image.pullPolicy }}" + args: + - --watched-dir=/etc/config + - --reload-url=http://127.0.0.1:9090{{ .Values.prometheus.server.prefixURL }}/-/reload + {{- range $key, $value := .Values.prometheus.configmapReload.prometheus.extraArgs }} + - --{{ $key }}={{ $value }} + {{- end }} + {{- range .Values.prometheus.configmapReload.prometheus.extraVolumeDirs }} + - --watched-dir={{ . }} + {{- end }} + resources: + {{- toYaml .Values.prometheus.configmapReload.prometheus.resources | nindent 12 }} + securityContext: + {{- if .Values.global.containerSecurityContext }} + {{- toYaml .Values.global.containerSecurityContext | nindent 12 }} + {{- else }} + {{- toYaml .Values.prometheus.configmapReload.prometheus.containerSecurityContext | nindent 12 }} + {{- end }} + volumeMounts: + {{- if .Values.prometheus.selfsignedCertConfigMapName }} + - name: {{ .Values.prometheus.selfsignedCertConfigMapName }} + mountPath: /etc/ssl/certs/my-cert.pem + subPath: my-cert.pem + readOnly: false + {{- end }} + - name: config-volume + mountPath: /etc/config + readOnly: true + {{- range .Values.prometheus.configmapReload.prometheus.extraConfigmapMounts }} + - name: {{ $.Values.prometheus.configmapReload.prometheus.name }}-{{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- end }} + + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.server.name }} + image: "{{ .Values.prometheus.server.image.repository }}:{{ .Values.prometheus.server.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.server.image.pullPolicy }}" + {{- if .Values.prometheus.server.env }} + env: +{{ toYaml .Values.prometheus.server.env | indent 12}} + {{- end }} + args: + {{- if .Values.prometheus.server.retention }} + - --storage.tsdb.retention.time={{ .Values.prometheus.server.retention }} + {{- end }} + {{- if .Values.prometheus.server.retentionSize }} + - --storage.tsdb.retention.size={{ .Values.prometheus.server.retentionSize }} + {{- end }} + - --config.file={{ .Values.prometheus.server.configPath }} + - --storage.tsdb.path={{ .Values.prometheus.server.persistentVolume.mountPath }} + - --web.console.libraries=/etc/prometheus/console_libraries + - --web.console.templates=/etc/prometheus/consoles + {{- range .Values.prometheus.server.extraFlags }} + - --{{ . }} + {{- end }} + {{- if .Values.prometheus.server.baseURL }} + - --web.external-url={{ .Values.prometheus.server.baseURL }} + {{- end }} + + {{- range $key, $value := .Values.prometheus.server.extraArgs }} + - --{{ $key }}={{ $value }} + {{- end }} + ports: + - containerPort: 9090 + readinessProbe: + httpGet: + path: {{ .Values.prometheus.server.prefixURL }}/-/ready + port: 9090 + initialDelaySeconds: {{ .Values.prometheus.server.readinessProbeInitialDelay }} + timeoutSeconds: {{ .Values.prometheus.server.readinessProbeTimeout }} + failureThreshold: {{ .Values.prometheus.server.readinessProbeFailureThreshold }} + successThreshold: {{ .Values.prometheus.server.readinessProbeSuccessThreshold }} + livenessProbe: + httpGet: + path: {{ .Values.prometheus.server.prefixURL }}/-/healthy + port: 9090 + initialDelaySeconds: {{ .Values.prometheus.server.livenessProbeInitialDelay }} + timeoutSeconds: {{ .Values.prometheus.server.livenessProbeTimeout }} + failureThreshold: {{ .Values.prometheus.server.livenessProbeFailureThreshold }} + successThreshold: {{ .Values.prometheus.server.livenessProbeSuccessThreshold }} + resources: + {{- toYaml .Values.prometheus.server.resources | nindent 12 }} + securityContext: + {{- if .Values.global.containerSecurityContext }} + {{- toYaml .Values.global.containerSecurityContext | nindent 12 }} + {{- else }} + {{- toYaml .Values.prometheus.server.containerSecurityContext | nindent 12 }} + {{- end }} + volumeMounts: + - name: config-volume + mountPath: /etc/config + - name: storage-volume + mountPath: {{ .Values.prometheus.server.persistentVolume.mountPath }} + subPath: "{{ .Values.prometheus.server.persistentVolume.subPath }}" + {{- range .Values.prometheus.server.extraHostPathMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- range .Values.prometheus.server.extraConfigmapMounts }} + - name: {{ $.Values.prometheus.server.name }}-{{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- range .Values.prometheus.server.extraSecretMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- if .Values.prometheus.server.extraVolumeMounts }} + {{ toYaml .Values.prometheus.server.extraVolumeMounts | nindent 12 }} + {{- end }} + {{- if .Values.prometheus.server.sidecarContainers }} + {{- toYaml .Values.prometheus.server.sidecarContainers | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.prometheus.imagePullSecrets | indent 0 }} + {{- end }} + {{- if .Values.prometheus.server.nodeSelector }} + nodeSelector: + {{- toYaml .Values.prometheus.server.nodeSelector | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.server.securityContext }} + securityContext: + {{- if not .Values.prometheus.server.securityContext.fsGroup }} + fsGroupChangePolicy: OnRootMismatch + fsGroup: 1001 + {{- end }} + {{- toYaml .Values.prometheus.server.securityContext | nindent 8 }} + {{- else if and (.Values.global.platforms.openshift.enabled) (.Values.global.platforms.openshift.securityContext) }} + securityContext: + {{- toYaml .Values.global.platforms.openshift.securityContext | nindent 8 }} + {{- else if .Values.global.securityContext }} + securityContext: + {{- toYaml .Values.global.securityContext | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.server.tolerations }} + tolerations: +{{ toYaml .Values.prometheus.server.tolerations | indent 8 }} + {{- end }} + {{- if .Values.prometheus.server.affinity }} + affinity: +{{ toYaml .Values.prometheus.server.affinity | indent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.prometheus.server.terminationGracePeriodSeconds }} + volumes: + {{- if .Values.prometheus.selfsignedCertConfigMapName }} + - name: {{ .Values.prometheus.selfsignedCertConfigMapName }} + configMap: + name: {{ .Values.prometheus.selfsignedCertConfigMapName }} + {{- end }} + - name: config-volume + configMap: + name: {{ if .Values.prometheus.server.configMapOverrideName }}{{ .Release.Name }}-{{ .Values.prometheus.server.configMapOverrideName }}{{- else }}{{ template "prometheus.server.fullname" . }}{{- end }} + - name: storage-volume + {{- if .Values.prometheus.server.persistentVolume.enabled }} + persistentVolumeClaim: + claimName: {{ if .Values.prometheus.server.persistentVolume.existingClaim }}{{ .Values.prometheus.server.persistentVolume.existingClaim }}{{- else }}{{ template "prometheus.server.fullname" . }}{{- end }} + {{- else }} + emptyDir: + {{- if .Values.prometheus.server.emptyDir.sizeLimit }} + sizeLimit: {{ .Values.prometheus.server.emptyDir.sizeLimit }} + {{- else }} + {} + {{- end -}} + {{- end -}} +{{- if .Values.prometheus.server.extraVolumes }} +{{ toYaml .Values.prometheus.server.extraVolumes | indent 8}} +{{- end }} + {{- range .Values.prometheus.server.extraHostPathMounts }} + - name: {{ .name }} + hostPath: + path: {{ .hostPath }} + {{- end }} + {{- range .Values.prometheus.configmapReload.prometheus.extraConfigmapMounts }} + - name: {{ $.Values.prometheus.configmapReload.prometheus.name }}-{{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} + {{- range .Values.prometheus.server.extraConfigmapMounts }} + - name: {{ $.Values.prometheus.server.name }}-{{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} + {{- range .Values.prometheus.server.extraSecretMounts }} + - name: {{ .name }} + secret: + secretName: {{ tpl .secretName $ }} + {{- end }} + {{- range .Values.prometheus.configmapReload.prometheus.extraConfigmapMounts }} + - name: {{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} +{{- end -}} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-ingress.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-ingress.yaml new file mode 100644 index 000000000..18a7835fc --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-ingress.yaml @@ -0,0 +1,45 @@ +{{- if and (.Values.global.prometheus.enabled) (.Values.prometheus.server.enabled) (.Values.prometheus.server.ingress.enabled) }} +{{- $serviceName := include "prometheus.server.fullname" . }} +{{- $servicePort := .Values.prometheus.server.service.servicePort -}} +{{- $extraPaths := .Values.prometheus.server.ingress.extraPaths -}} +{{- $pathType := .Values.prometheus.server.ingress.pathType -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: +{{- if .Values.prometheus.server.ingress.annotations }} + annotations: +{{ toYaml .Values.prometheus.server.ingress.annotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} +{{- range $key, $value := .Values.prometheus.server.ingress.extraLabels }} + {{ $key }}: {{ $value }} +{{- end }} + name: {{ template "prometheus.server.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: +{{- if .Values.prometheus.server.ingress.className }} + ingressClassName: {{ .Values.prometheus.server.ingress.className }} +{{- end }} + rules: + {{- range .Values.prometheus.server.ingress.hosts }} + {{- $url := splitList "/" . }} + - host: {{ first $url }} + http: + paths: +{{ if $extraPaths }} +{{ toYaml $extraPaths | indent 10 }} +{{- end }} + - path: /{{ rest $url | join "/" }} + pathType: {{ $pathType }} + backend: + service: + name: {{ $serviceName }} + port: + number: {{ $servicePort }} + {{- end -}} +{{- if .Values.prometheus.server.ingress.tls }} + tls: +{{ toYaml .Values.prometheus.server.ingress.tls | indent 4 }} + {{- end -}} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-networkpolicy.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-networkpolicy.yaml new file mode 100644 index 000000000..23b04419c --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-networkpolicy.yaml @@ -0,0 +1,16 @@ +{{- if and (.Values.global.prometheus.enabled) (.Values.prometheus.server.enabled) (.Values.networkPolicy.enabled) }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ template "prometheus.server.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "prometheus.server.matchLabels" . | nindent 6 }} + ingress: + - ports: + - port: 9090 +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-pdb.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-pdb.yaml new file mode 100644 index 000000000..52ceeb248 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-pdb.yaml @@ -0,0 +1,15 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.server.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ template "prometheus.server.fullname" . }} + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} +spec: + maxUnavailable: {{ .Values.prometheus.server.podDisruptionBudget.maxUnavailable }} + selector: + matchLabels: + {{- include "prometheus.server.labels" . | nindent 6 }} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-pvc.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-pvc.yaml new file mode 100644 index 000000000..301a33e1a --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-pvc.yaml @@ -0,0 +1,37 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.server.enabled -}} +{{- if not .Values.prometheus.server.statefulSet.enabled -}} +{{- if .Values.prometheus.server.persistentVolume.enabled -}} +{{- if not .Values.prometheus.server.persistentVolume.existingClaim -}} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + {{- if .Values.prometheus.server.persistentVolume.annotations }} + annotations: +{{ toYaml .Values.prometheus.server.persistentVolume.annotations | indent 4 }} + {{- end }} + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} + name: {{ template "prometheus.server.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + accessModes: +{{ toYaml .Values.prometheus.server.persistentVolume.accessModes | indent 4 }} +{{- if .Values.prometheus.server.persistentVolume.storageClass }} +{{- if (eq "-" .Values.prometheus.server.persistentVolume.storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: "{{ .Values.prometheus.server.persistentVolume.storageClass }}" +{{- end }} +{{- end }} +{{- if .Values.prometheus.server.persistentVolume.volumeBindingMode }} + volumeBindingModeName: "{{ .Values.prometheus.server.persistentVolume.volumeBindingMode }}" +{{- end }} + resources: + requests: + storage: "{{ .Values.prometheus.server.persistentVolume.size }}" +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-service-headless.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-service-headless.yaml new file mode 100644 index 000000000..019803d30 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-service-headless.yaml @@ -0,0 +1,29 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.server.enabled -}} +{{- if .Values.prometheus.server.statefulSet.enabled -}} +apiVersion: v1 +kind: Service +metadata: +{{- if .Values.prometheus.server.statefulSet.headless.annotations }} + annotations: +{{ toYaml .Values.prometheus.server.statefulSet.headless.annotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} +{{- if .Values.prometheus.server.statefulSet.headless.labels }} +{{ toYaml .Values.prometheus.server.statefulSet.headless.labels | indent 4 }} +{{- end }} + name: {{ template "prometheus.server.fullname" . }}-headless + namespace: {{ .Release.Namespace }} +spec: + clusterIP: None + ports: + - name: http + port: {{ .Values.prometheus.server.statefulSet.headless.servicePort }} + protocol: TCP + targetPort: 9090 + selector: + {{- include "prometheus.server.matchLabels" . | nindent 4 }} +{{- end -}} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-service.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-service.yaml new file mode 100644 index 000000000..69f093c38 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-service.yaml @@ -0,0 +1,62 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.server.enabled -}} +apiVersion: v1 +kind: Service +metadata: +{{- if .Values.prometheus.server.service.annotations }} + annotations: +{{ toYaml .Values.prometheus.server.service.annotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} +{{- if .Values.prometheus.server.service.labels }} +{{ toYaml .Values.prometheus.server.service.labels | indent 4 }} +{{- end }} + name: {{ template "prometheus.server.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: +{{- if .Values.prometheus.server.service.clusterIP }} + clusterIP: {{ .Values.prometheus.server.service.clusterIP }} +{{- end }} +{{- if .Values.prometheus.server.service.externalIPs }} + externalIPs: +{{ toYaml .Values.prometheus.server.service.externalIPs | indent 4 }} +{{- end }} +{{- if .Values.prometheus.server.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.prometheus.server.service.loadBalancerIP }} +{{- end }} +{{- if .Values.prometheus.server.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- range $cidr := .Values.prometheus.server.service.loadBalancerSourceRanges }} + - {{ $cidr }} + {{- end }} +{{- end }} + ports: + - name: http + port: {{ .Values.prometheus.server.service.servicePort }} + protocol: TCP + targetPort: 9090 + {{- if .Values.prometheus.server.service.nodePort }} + nodePort: {{ .Values.prometheus.server.service.nodePort }} + {{- end }} + {{- if .Values.prometheus.server.service.gRPC.enabled }} + - name: grpc + port: {{ .Values.prometheus.server.service.gRPC.servicePort }} + protocol: TCP + targetPort: 10901 + {{- if .Values.prometheus.server.service.gRPC.nodePort }} + nodePort: {{ .Values.prometheus.server.service.gRPC.nodePort }} + {{- end }} + {{- end }} + selector: + {{- if and .Values.prometheus.server.statefulSet.enabled .Values.prometheus.server.service.statefulsetReplica.enabled }} + statefulset.kubernetes.io/pod-name: {{ .Release.Name }}-{{ .Values.prometheus.server.name }}-{{ .Values.prometheus.server.service.statefulsetReplica.replica }} + {{- else -}} + {{- include "prometheus.server.matchLabels" . | nindent 4 }} +{{- if .Values.prometheus.server.service.sessionAffinity }} + sessionAffinity: {{ .Values.prometheus.server.service.sessionAffinity }} +{{- end }} + {{- end }} + type: "{{ .Values.prometheus.server.service.type }}" +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-serviceaccount.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-serviceaccount.yaml new file mode 100644 index 000000000..17ee234bb --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-serviceaccount.yaml @@ -0,0 +1,17 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.server.enabled -}} +{{- if .Values.prometheus.serviceAccounts.server.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} + name: {{ template "prometheus.serviceAccountName.server" . }} + namespace: {{ .Release.Namespace }} + {{- with .Values.prometheus.serviceAccounts.server.annotations }} + annotations: + {{- . | toYaml | nindent 4 }} + {{- end }} +{{- end }} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-statefulset.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-statefulset.yaml new file mode 100644 index 000000000..aba286811 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-statefulset.yaml @@ -0,0 +1,227 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.server.enabled -}} +{{- if .Values.prometheus.server.statefulSet.enabled -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: +{{- if .Values.prometheus.server.statefulSet.annotations }} + annotations: +{{ toYaml .Values.prometheus.server.statefulSet.annotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if .Values.prometheus.server.statefulSet.labels}} + {{ toYaml .Values.prometheus.server.statefulSet.labels | nindent 4 }} + {{- end}} + name: {{ template "prometheus.server.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + serviceName: {{ template "prometheus.server.fullname" . }}-headless + selector: + matchLabels: + {{- include "prometheus.server.matchLabels" . | nindent 6 }} + replicas: {{ .Values.prometheus.server.replicaCount }} + podManagementPolicy: {{ .Values.prometheus.server.statefulSet.podManagementPolicy }} + template: + metadata: + {{- if .Values.prometheus.server.podAnnotations }} + annotations: +{{ toYaml .Values.prometheus.server.podAnnotations | indent 8 }} + {{- end }} + labels: + {{- include "prometheus.server.labels" . | nindent 8 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.server.statefulSet.labels}} + {{ toYaml .Values.prometheus.server.statefulSet.labels | nindent 8 }} + {{- end}} + spec: +{{- if .Values.prometheus.server.priorityClassName }} + priorityClassName: "{{ .Values.prometheus.server.priorityClassName }}" +{{- end }} +{{- if .Values.prometheus.server.schedulerName }} + schedulerName: "{{ .Values.prometheus.server.schedulerName }}" +{{- end }} + serviceAccountName: {{ template "prometheus.serviceAccountName.server" . }} + containers: + {{- if .Values.prometheus.configmapReload.prometheus.enabled }} + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.server.name }}-{{ .Values.prometheus.configmapReload.prometheus.name }} + image: "{{ .Values.prometheus.configmapReload.prometheus.image.repository }}:{{ .Values.prometheus.configmapReload.prometheus.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.configmapReload.prometheus.image.pullPolicy }}" + args: + - --watched-dir=/etc/config + - --reload-url=http://127.0.0.1:9090{{ .Values.prometheus.server.prefixURL }}/-/reload + {{- range $key, $value := .Values.prometheus.configmapReload.prometheus.extraArgs }} + - --{{ $key }}={{ $value }} + {{- end }} + {{- range .Values.prometheus.configmapReload.prometheus.extraVolumeDirs }} + - --watched-dir={{ . }} + {{- end }} + resources: +{{ toYaml .Values.prometheus.configmapReload.prometheus.resources | indent 12 }} + volumeMounts: + - name: config-volume + mountPath: /etc/config + readOnly: true + {{- range .Values.prometheus.configmapReload.prometheus.extraConfigmapMounts }} + - name: {{ $.Values.prometheus.configmapReload.prometheus.name }}-{{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- end }} + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.server.name }} + image: "{{ .Values.prometheus.server.image.repository }}:{{ .Values.prometheus.server.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.server.image.pullPolicy }}" + {{- if .Values.prometheus.server.env }} + env: +{{ toYaml .Values.prometheus.server.env | indent 12}} + {{- end }} + args: + {{- if .Values.prometheus.server.retention }} + - --storage.tsdb.retention.time={{ .Values.prometheus.server.retention }} + {{- end }} + - --config.file={{ .Values.prometheus.server.configPath }} + - --storage.tsdb.path={{ .Values.prometheus.server.persistentVolume.mountPath }} + - --web.console.libraries=/etc/prometheus/console_libraries + - --web.console.templates=/etc/prometheus/consoles + {{- range .Values.prometheus.server.extraFlags }} + - --{{ . }} + {{- end }} + {{- range $key, $value := .Values.prometheus.server.extraArgs }} + - --{{ $key }}={{ $value }} + {{- end }} + {{- if .Values.prometheus.server.baseURL }} + - --web.external-url={{ .Values.prometheus.server.baseURL }} + {{- end }} + ports: + - containerPort: 9090 + readinessProbe: + httpGet: + path: {{ .Values.prometheus.server.prefixURL }}/-/ready + port: 9090 + initialDelaySeconds: {{ .Values.prometheus.server.readinessProbeInitialDelay }} + timeoutSeconds: {{ .Values.prometheus.server.readinessProbeTimeout }} + livenessProbe: + httpGet: + path: {{ .Values.prometheus.server.prefixURL }}/-/healthy + port: 9090 + initialDelaySeconds: {{ .Values.prometheus.server.livenessProbeInitialDelay }} + timeoutSeconds: {{ .Values.prometheus.server.livenessProbeTimeout }} + resources: +{{ toYaml .Values.prometheus.server.resources | indent 12 }} + volumeMounts: + - name: config-volume + mountPath: /etc/config + - name: storage-volume + mountPath: {{ .Values.prometheus.server.persistentVolume.mountPath }} + subPath: "{{ .Values.prometheus.server.persistentVolume.subPath }}" + {{- range .Values.prometheus.server.extraHostPathMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- range .Values.prometheus.server.extraConfigmapMounts }} + - name: {{ $.Values.prometheus.server.name }}-{{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- range .Values.prometheus.server.extraSecretMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- if .Values.prometheus.server.extraVolumeMounts }} + {{ toYaml .Values.prometheus.server.extraVolumeMounts | nindent 12 }} + {{- end }} + {{- if .Values.prometheus.server.sidecarContainers }} + {{- toYaml .Values.prometheus.server.sidecarContainers | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.prometheus.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.prometheus.server.nodeSelector }} + nodeSelector: +{{ toYaml .Values.prometheus.server.nodeSelector | indent 8 }} + {{- end }} + {{- if .Values.prometheus.server.securityContext }} + securityContext: +{{ toYaml .Values.prometheus.server.securityContext | indent 8 }} + {{- end }} + {{- if .Values.prometheus.server.tolerations }} + tolerations: +{{ toYaml .Values.prometheus.server.tolerations | indent 8 }} + {{- end }} + {{- if .Values.prometheus.server.affinity }} + affinity: +{{ toYaml .Values.prometheus.server.affinity | indent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.prometheus.server.terminationGracePeriodSeconds }} + volumes: + - name: config-volume + configMap: + name: {{ if .Values.prometheus.server.configMapOverrideName }}{{ .Release.Name }}-{{ .Values.prometheus.server.configMapOverrideName }}{{- else }}{{ template "prometheus.server.fullname" . }}{{- end }} + {{- range .Values.prometheus.server.extraHostPathMounts }} + - name: {{ .name }} + hostPath: + path: {{ .hostPath }} + {{- end }} + {{- range .Values.prometheus.configmapReload.prometheus.extraConfigmapMounts }} + - name: {{ $.Values.prometheus.configmapReload.prometheus.name }}-{{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} + {{- range .Values.prometheus.server.extraConfigmapMounts }} + - name: {{ $.Values.prometheus.server.name }}-{{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} + {{- range .Values.prometheus.server.extraSecretMounts }} + - name: {{ .name }} + secret: + secretName: {{ .secretName }} + {{- end }} + {{- range .Values.prometheus.configmapReload.prometheus.extraConfigmapMounts }} + - name: {{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} +{{- if .Values.prometheus.server.extraVolumes }} +{{ toYaml .Values.prometheus.server.extraVolumes | indent 8}} +{{- end }} +{{- if .Values.prometheus.server.persistentVolume.enabled }} + volumeClaimTemplates: + - metadata: + name: storage-volume + {{- if .Values.prometheus.server.persistentVolume.annotations }} + annotations: +{{ toYaml .Values.prometheus.server.persistentVolume.annotations | indent 10 }} + {{- end }} + spec: + accessModes: +{{ toYaml .Values.prometheus.server.persistentVolume.accessModes | indent 10 }} + resources: + requests: + storage: "{{ .Values.prometheus.server.persistentVolume.size }}" + {{- if .Values.prometheus.server.persistentVolume.storageClass }} + {{- if (eq "-" .Values.prometheus.server.persistentVolume.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ .Values.prometheus.server.persistentVolume.storageClass }}" + {{- end }} + {{- end }} +{{- else }} + - name: storage-volume + emptyDir: {} +{{- end }} +{{- end }} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-vpa.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-vpa.yaml new file mode 100644 index 000000000..25a61f253 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/prometheus-server-vpa.yaml @@ -0,0 +1,22 @@ +{{- if and (.Values.global.prometheus.enabled) (.Values.prometheus.server.enabled) (.Values.prometheus.server.verticalAutoscaler.enabled) }} +apiVersion: autoscaling.k8s.io/v1 +kind: VerticalPodAutoscaler +metadata: + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} + name: {{ template "prometheus.server.fullname" . }}-vpa + namespace: {{ .Release.Namespace }} +spec: + targetRef: + apiVersion: apps/v1 +{{- if .Values.prometheus.server.statefulSet.enabled }} + kind: StatefulSet +{{- else }} + kind: Deployment +{{- end }} + name: {{ template "prometheus.server.fullname" . }} + updatePolicy: + updateMode: {{ .Values.prometheus.server.verticalAutoscaler.updateMode | default "Off" | quote }} + resourcePolicy: + containerPolicies: {{ .Values.prometheus.server.verticalAutoscaler.containerPolicies | default list | toYaml | trim | nindent 4 }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/tests/_helpers.tpl b/charts/kubecost/cost-analyzer/2.3.4/templates/tests/_helpers.tpl new file mode 100644 index 000000000..8c1e53a56 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/tests/_helpers.tpl @@ -0,0 +1,5 @@ +{{/* vim: set filetype=mustache: */}} + +{{- define "kubecost.test.annotations" -}} +helm.sh/hook: test +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.4/templates/tests/basic-health.yaml b/charts/kubecost/cost-analyzer/2.3.4/templates/tests/basic-health.yaml new file mode 100644 index 000000000..e092b54fd --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/templates/tests/basic-health.yaml @@ -0,0 +1,48 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: basic-health + namespace: {{ .Release.Namespace }} + annotations: + {{- include "kubecost.test.annotations" . | nindent 4 }} + labels: + app: basic-health + app.kubernetes.io/name: basic-health + app.kubernetes.io/instance: {{ .Release.Name }} +spec: + automountServiceAccountToken: false + restartPolicy: Never + securityContext: + seccompProfile: + type: RuntimeDefault + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 3000 + fsGroup: 2000 + containers: + - name: test-kubecost + image: alpine/k8s:1.26.9 + securityContext: + privileged: false + capabilities: + drop: + - ALL + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + command: + - /bin/sh + args: + - -c + - >- + svc="{{ .Release.Name }}-cost-analyzer"; + echo Getting current Kubecost state.; + response=$(curl -sL http://${svc}:9090/model/getConfigs); + code=$(echo ${response} | jq .code); + if [ "$code" -eq 200 ]; then + echo "Got Kubecost working configuration. Successful." + exit 0 + else + echo "Failed to fetch Kubecost configuration. Response was $response" + exit 1 + fi diff --git a/charts/kubecost/cost-analyzer/2.3.4/values-agent.yaml b/charts/kubecost/cost-analyzer/2.3.4/values-agent.yaml new file mode 100644 index 000000000..39320e0ed --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/values-agent.yaml @@ -0,0 +1,113 @@ + +# Kubecost running as an Agent is designed for external hosting. The current setup deploys a +# kubecost-agent pod, low data retention prometheus server + thanos sidecar, and node-exporter. +networkCosts: + enabled: false + # config: + # services: + # amazon-web-services: true + # google-cloud-services: true + # azure-cloud-services: true +thanos: + storeSecretName: kubecost-agent-object-store + +global: + thanos: + enabled: false + grafana: + enabled: false + proxy: false +# Agent enables specific features designed to enhance the metrics exporter deployment +# with enhancements designed for external hosting. +# agent: true +# agentKeySecretName: kubecost-agent-object-store +agentCsi: + enabled: false + secretProvider: + name: kubecost-agent-object-store-secretprovider + provider: + parameters: {} + secretObjects: {} + +kubecostFrontend: + enabled: false + +# Exporter Pod +# kubecostMetrics: +# exporter: +# enabled: true +# exportClusterInfo: true +# exportClusterCache: true + +# Prometheus defaults to low retention (10h), disables KSM, and attaches a thanos-sidecar +# for exporting metrics. +prometheus: + nodeExporter: + enabled: false + extraScrapeConfigs: | + - job_name: kubecost-agent + honor_labels: true + scrape_interval: 1m + scrape_timeout: 60s + metrics_path: /metrics + scheme: http + dns_sd_configs: + - names: + - kubecost-agent-agent + type: 'A' + port: 9005 + - job_name: kubecost-networking + kubernetes_sd_configs: + - role: pod + relabel_configs: + # Scrape only the the targets matching the following metadata + - source_labels: [__meta_kubernetes_pod_label_app] + action: keep + regex: {{ template "cost-analyzer.networkCostsName" . }} + server: + retention: 50h + # retentionSize: 1Gi + extraArgs: + storage.tsdb.min-block-duration: 2h + storage.tsdb.max-block-duration: 2h + securityContext: + runAsNonRoot: true + runAsUser: 1001 + extraSecretMounts: + - name: object-store-volume + mountPath: /etc/thanos/config + readOnly: true + secretName: kubecost-agent-object-store + enableAdminApi: true + sidecarContainers: + - name: thanos-sidecar + image: thanosio/thanos:v0.34.0 + securityContext: + runAsNonRoot: true + runAsUser: 1001 + args: + - sidecar + - --log.level=debug + - --tsdb.path=/data/ + - --prometheus.url=http://127.0.0.1:9090 + - --objstore.config-file=/etc/thanos/config/object-store.yaml + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + ports: + - name: sidecar-http + containerPort: 10902 + - name: grpc + containerPort: 10901 + - name: cluster + containerPort: 10900 + volumeMounts: + - name: config-volume + mountPath: /etc/prometheus + - name: storage-volume + mountPath: /data + subPath: "" + - name: object-store-volume + mountPath: /etc/thanos/config diff --git a/charts/kubecost/cost-analyzer/2.3.4/values-amp.yaml b/charts/kubecost/cost-analyzer/2.3.4/values-amp.yaml new file mode 100644 index 000000000..4242ba300 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/values-amp.yaml @@ -0,0 +1,20 @@ +global: + amp: + enabled: true + prometheusServerEndpoint: http://localhost:8005/workspaces/$ + remoteWriteService: https://aps-workspaces.us-west-2.amazonaws.com/workspaces/$/api/v1/remote_write + sigv4: + region: us-west-2 + +sigV4Proxy: + region: us-west-2 + host: aps-workspaces.us-west-2.amazonaws.com + +kubecostProductConfigs: + clusterName: AWS-cluster-one + +prometheus: + server: + global: + external_labels: + cluster_id: AWS-cluster-one \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.4/values-cloud-agent.yaml b/charts/kubecost/cost-analyzer/2.3.4/values-cloud-agent.yaml new file mode 100644 index 000000000..3f2436925 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/values-cloud-agent.yaml @@ -0,0 +1,45 @@ +# Kubecost running as an Agent is designed for external hosting. The current setup deploys a +# kubecost-agent pod and prometheus server +global: + thanos: + enabled: false + grafana: + enabled: false + proxy: false + +# Cloud Agent enables specific features designed to enhance the metrics exporter deployment +# with enhancements designed for Kubecost Cloud +cloudAgent: true +cloudAgentKey: "" + +# No Grafana configuration is required. +grafana: + sidecar: + dashboards: + enabled: false + datasources: + defaultDatasourceEnabled: false + +# Exporter Pod +kubecostMetrics: + exporter: + enabled: true + exportClusterInfo: false + exportClusterCache: false + +# Disable KSM and NodeExporter (?) +prometheus: + nodeExporter: + enabled: false + extraScrapeConfigs: | + - job_name: kubecost-cloud-agent + honor_labels: true + scrape_interval: 1m + scrape_timeout: 60s + metrics_path: /metrics + scheme: http + dns_sd_configs: + - names: + - {{ .Release.Name }}-cloud-agent + type: 'A' + port: 9005 diff --git a/charts/kubecost/cost-analyzer/2.3.4/values-custom-pricing.yaml b/charts/kubecost/cost-analyzer/2.3.4/values-custom-pricing.yaml new file mode 100644 index 000000000..82a0c5540 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/values-custom-pricing.yaml @@ -0,0 +1,17 @@ +pricingCsv: + enabled: true + location: + URI: /var/kubecost-csv/custom-pricing.csv # local configMap or s3://bucket/path/custom-pricing.csv + # provider: "AWS" + # region: "us-east-1" + # URI: s3://kc-csv-test/pricing_schema.csv # a valid file URI + # csvAccessCredentials: pricing-schema-access-secret + +# when using configmap: kubectl create configmap -n kubecost csv-pricing --from-file custom-pricing.csv +extraVolumes: +- name: kubecost-csv + configMap: + name: csv-pricing +extraVolumeMounts: +- name: kubecost-csv + mountPath: /var/kubecost-csv diff --git a/charts/kubecost/cost-analyzer/2.3.4/values-eks-cost-monitoring.yaml b/charts/kubecost/cost-analyzer/2.3.4/values-eks-cost-monitoring.yaml new file mode 100644 index 000000000..f8619429a --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/values-eks-cost-monitoring.yaml @@ -0,0 +1,43 @@ +# grafana is disabled by default, but can be enabled by setting the following values. +# or proxy to an existing grafana: https://docs.kubecost.com/install-and-configure/advanced-configuration/custom-grafana +global: + grafana: + enabled: false + proxy: false +# grafana: +# image: +# repository: YOUR_REGISTRY/grafana +# sidecar: +# image: +# repository: YOUR_REGISTRY/k8s-sidecar + +kubecostFrontend: + image: public.ecr.aws/kubecost/frontend + +kubecostModel: + image: public.ecr.aws/kubecost/cost-model + +forecasting: + fullImageName: public.ecr.aws/kubecost/kubecost-modeling:v0.1.12 + +networkCosts: + image: + repository: public.ecr.aws/kubecost/kubecost-network-costs + +clusterController: + image: + repository: public.ecr.aws/kubecost/cluster-controller + +prometheus: + server: + image: + repository: public.ecr.aws/kubecost/prometheus + + configmapReload: + prometheus: + image: + repository: public.ecr.aws/kubecost/prometheus-config-reloader + +reporting: + productAnalytics: false + diff --git a/charts/kubecost/cost-analyzer/2.3.4/values-openshift.yaml b/charts/kubecost/cost-analyzer/2.3.4/values-openshift.yaml new file mode 100644 index 000000000..7c8ea13b3 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/values-openshift.yaml @@ -0,0 +1,25 @@ +global: + # Platforms is a higher-level abstraction for platform-specific values and settings. + platforms: + # Deploying to OpenShift (OCP) requires enabling this option. + openshift: + enabled: true # Deploy Kubecost to OpenShift. + route: + enabled: false # Create an OpenShift Route. + annotations: {} # Add annotations to the Route. + # host: kubecost.apps.okd4.example.com # Add a custom host for your Route. + # Create Security Context Constraint resources for the DaemonSets requiring additional privileges. + scc: + nodeExporter: false # Creates an SCC for Prometheus Node Exporter. This requires Node Exporter be enabled. + networkCosts: false # Creates an SCC for Kubecost network-costs. This requires network-costs be enabled. + # When OpenShift is enabled, the following securityContext will be applied to all resources unless they define their own. + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + +# networkCosts: +# enabled: true # Enable network costs. +# prometheus: +# nodeExporter: +# enabled: true # Enable Prometheus Node Exporter. diff --git a/charts/kubecost/cost-analyzer/2.3.4/values-windows-node-affinity.yaml b/charts/kubecost/cost-analyzer/2.3.4/values-windows-node-affinity.yaml new file mode 100644 index 000000000..5770f0c12 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/values-windows-node-affinity.yaml @@ -0,0 +1,30 @@ +kubecostMetrics: + exporter: + nodeSelector: + kubernetes.io/os: linux + +nodeSelector: + kubernetes.io/os: linux + +networkCosts: + nodeSelector: + kubernetes.io/os: linux + +prometheus: + server: + nodeSelector: + kubernetes.io/os: linux + nodeExporter: + enabled: true + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/os + operator: In + values: + - linux +grafana: + nodeSelector: + kubernetes.io/os: linux diff --git a/charts/kubecost/cost-analyzer/2.3.4/values.yaml b/charts/kubecost/cost-analyzer/2.3.4/values.yaml new file mode 100644 index 000000000..937b993f3 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.4/values.yaml @@ -0,0 +1,3453 @@ +global: + # zone: cluster.local (use only if your DNS server doesn't live in the same zone as kubecost) + prometheus: + enabled: true # Kubecost depends on Prometheus data, it is not optional. When enabled: false, Prometheus will not be installed and you must configure your own Prometheus to scrape kubecost as well as provide the fqdn below. -- Warning: Before changing this setting, please read to understand the risks https://docs.kubecost.com/install-and-configure/install/custom-prom + fqdn: http://cost-analyzer-prometheus-server.default.svc # example address of a prometheus to connect to. Include protocol (http:// or https://) Ignored if enabled: true + # insecureSkipVerify: false # If true, kubecost will not check the TLS cert of prometheus + # queryServiceBasicAuthSecretName: dbsecret # kubectl create secret generic dbsecret -n kubecost --from-file=USERNAME --from-file=PASSWORD + # queryServiceBearerTokenSecretName: mcdbsecret # kubectl create secret generic mcdbsecret -n kubecost --from-file=TOKEN + + grafana: + enabled: true # If false, Grafana will not be installed + domainName: cost-analyzer-grafana.default.svc # example grafana domain Ignored if enabled: true + scheme: "http" # http or https, for the domain name above. + proxy: true # If true, the kubecost frontend will route to your grafana through its service endpoint + # fqdn: cost-analyzer-grafana.default.svc + + # Enable only when you are using GCP Marketplace ENT listing. Learn more at https://console.cloud.google.com/marketplace/product/kubecost-public/kubecost-ent + gcpstore: + enabled: false + + # Google Cloud Managed Service for Prometheus + gmp: + # Remember to set up these parameters when install the Kubecost Helm chart with `global.gmp.enabled=true` if you want to use GMP self-deployed collection (Recommended) to utilize Kubecost scrape configs. + # If enabling GMP, it is highly recommended to utilize Google's distribution of Prometheus. + # Learn more at https://cloud.google.com/stackdriver/docs/managed-prometheus/setup-unmanaged + # --set prometheus.server.image.repository="gke.gcr.io/prometheus-engine/prometheus" \ + # --set prometheus.server.image.tag="v2.35.0-gmp.2-gke.0" + enabled: false # If true, kubecost will be configured to use GMP Prometheus image and query from Google Cloud Managed Service for Prometheus. + prometheusServerEndpoint: http://localhost:8085/ # The prometheus service endpoint used by kubecost. The calls are forwarded through the GMP Prom proxy side car to the GMP database. + gmpProxy: + enabled: false + image: gke.gcr.io/prometheus-engine/frontend:v0.4.1-gke.0 # GMP Prometheus proxy image that serve as an endpoint to query metrics from GMP + imagePullPolicy: IfNotPresent + name: gmp-proxy + port: 8085 + projectId: YOUR_PROJECT_ID # example GCP project ID + + # Amazon Managed Service for Prometheus + amp: + enabled: false # If true, kubecost will be configured to remote_write and query from Amazon Managed Service for Prometheus. + prometheusServerEndpoint: http://localhost:8005/workspaces// # The prometheus service endpoint used by kubecost. The calls are forwarded through the SigV4Proxy side car to the AMP workspace. + remoteWriteService: https://aps-workspaces.us-west-2.amazonaws.com/workspaces//api/v1/remote_write # The remote_write endpoint for the AMP workspace. + sigv4: + region: us-west-2 + # access_key: ACCESS_KEY # AWS Access key + # secret_key: SECRET_KEY # AWS Secret key + # role_arn: ROLE_ARN # AWS role arn + # profile: PROFILE # AWS profile + + # Mimir Proxy to help Kubecost to query metrics from multi-tenant Grafana Mimir. + # Set `global.mimirProxy.enabled=true` and `global.prometheus.enabled=false` to enable Mimir Proxy. + # You also need to set `global.prometheus.fqdn=http://kubecost-cost-analyzer-mimir-proxy.kubecost.svc:8085/prometheus` + # or `global.prometheus.fqdn=http://{{ template "cost-analyzer.fullname" . }}-mimir-proxy.{{ .Release.Namespace }}.svc:8085/prometheus' + # Learn more at https://grafana.com/docs/mimir/latest/operators-guide/secure/authentication-and-authorization/#without-an-authenticating-reverse-proxy + mimirProxy: + enabled: false + name: mimir-proxy + image: nginxinc/nginx-unprivileged + port: 8085 + mimirEndpoint: $mimir_endpoint # Your Mimir query endpoint. If your Mimir query endpoint is http://example.com/prometheus, replace $mimir_endpoint with http://example.com/ + orgIdentifier: $your_tenant_ID # Your Grafana Mimir tenant ID + # basicAuth: + # username: user + # password: pwd + + notifications: + # Kubecost alerting configuration + # Ref: http://docs.kubecost.com/alerts + # alertConfigs: + # frontendUrl: http://localhost:9090 # optional, used for linkbacks + # globalSlackWebhookUrl: https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX # optional, used for Slack alerts + # globalMsTeamsWebhookUrl: https://xxxxx.webhook.office.com/webhookb2/XXXXXXXXXXXXXXXXXXXXXXXX/IncomingWebhook/XXXXXXXXXXXXXXXXXXXXXXXX # optional, used for Microsoft Teams alerts + # globalAlertEmails: + # - recipient@example.com + # - additionalRecipient@example.com + # globalEmailSubject: Custom Subject + # Alerts generated by kubecost, about cluster data + # alerts: + # Daily namespace budget alert on namespace `kubecost` + # - type: budget # supported: budget, recurringUpdate + # threshold: 50 # optional, required for budget alerts + # window: daily # or 1d + # aggregation: namespace + # filter: kubecost + # ownerContact: # optional, overrides globalAlertEmails default + # - owner@example.com + # - owner2@example.com + # # optional, used for alert-specific Slack and Microsoft Teams alerts + # slackWebhookUrl: https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX + # msTeamsWebhookUrl: https://xxxxx.webhook.office.com/webhookb2/XXXXXXXXXXXXXXXXXXXXXXXX/IncomingWebhook/XXXXXXXXXXXXXXXXXXXXXXXX + + # Daily cluster budget alert on cluster `cluster-one` + # - type: budget + # threshold: 200.8 # optional, required for budget alerts + # window: daily # or 1d + # aggregation: cluster + # filter: cluster-one # does not accept csv + + # Recurring weekly update (weeklyUpdate alert) + # - type: recurringUpdate + # window: weekly # or 7d + # aggregation: namespace + # filter: '*' + + # Recurring weekly namespace update on kubecost namespace + # - type: recurringUpdate + # window: weekly # or 7d + # aggregation: namespace + # filter: kubecost + + # Spend Change Alert + # - type: spendChange # change relative to moving avg + # relativeThreshold: 0.20 # Proportional change relative to baseline. Must be greater than -1 (can be negative) + # window: 1d # accepts ‘d’, ‘h’ + # baselineWindow: 30d # previous window, offset by window + # aggregation: namespace + # filter: kubecost, default # accepts csv + + # Health Score Alert + # - type: health # Alerts when health score changes by a threshold + # window: 10m + # threshold: 5 # Send Alert if health scores changes by 5 or more + + # Kubecost Health Diagnostic + # - type: diagnostic # Alerts when kubecost is unable to compute costs - ie: Prometheus unreachable + # window: 10m + + alertmanager: # Supply an alertmanager FQDN to receive notifications from the app. + enabled: false # If true, allow kubecost to write to your alertmanager + fqdn: http://cost-analyzer-prometheus-server.default.svc # example fqdn. Ignored if prometheus.enabled: true + + # Set saved Cost Allocation report(s) accessible from /reports + # Ref: http://docs.kubecost.com/saved-reports + savedReports: + enabled: false # If true, overwrites report parameters set through UI + reports: + - title: "Example Saved Report 0" + window: "today" + aggregateBy: "namespace" + chartDisplay: "category" + idle: "separate" + rate: "cumulative" + accumulate: false # daily resolution + filters: # Ref: https://docs.kubecost.com/apis/filters-api + - key: "cluster" # Ref: https://docs.kubecost.com/apis/filters-api#allocation-apis-request-sizing-v2-api + operator: ":" # Ref: https://docs.kubecost.com/apis/filters-api#filter-operators + value: "dev" + - title: "Example Saved Report 1" + window: "month" + aggregateBy: "controllerKind" + chartDisplay: "category" + idle: "share" + rate: "monthly" + accumulate: false + filters: # Ref: https://docs.kubecost.com/apis/filters-api + - key: "namespace" # Ref: https://docs.kubecost.com/apis/filters-api#allocation-apis-request-sizing-v2-api + operator: "!:" # Ref: https://docs.kubecost.com/apis/filters-api#filter-operators + value: "kubecost" + - title: "Example Saved Report 2" + window: "2020-11-11T00:00:00Z,2020-12-09T23:59:59Z" + aggregateBy: "service" + chartDisplay: "category" + idle: "hide" + rate: "daily" + accumulate: true # entire window resolution + filters: [] # if no filters, specify empty array + + # Set saved Asset report(s) accessible from /reports + # Ref: http://docs.kubecost.com/saved-reports + assetReports: + enabled: false # If true, overwrites report parameters set through UI + reports: + - title: "Example Asset Report 0" + window: "today" + aggregateBy: "type" + accumulate: false # daily resolution + filters: + - property: "cluster" + value: "cluster-one" + + # Set saved Advanced report(s) accessible from /reports + # Ref: http://docs.kubecost.com/saved-reports + advancedReports: + enabled: false # If true, overwrites report parameters set through UI + reports: + - title: "Example Advanced Report 0" + window: "7d" + aggregateBy: "namespace" + filters: # same as allocation api filters Ref: https://docs.kubecost.com/apis/filters-api + - key: "cluster" # Ref: https://docs.kubecost.com/apis/filters-api#allocation-apis-request-sizing-v2-api + operator: ":" # Ref: https://docs.kubecost.com/apis/filters-api#filter-operators + value: "dev" + cloudBreakdown: "service" + cloudJoin: "label:kubernetes_namespace" + + # Set saved Cloud Cost report(s) accessible from /reports + # Ref: http://docs.kubecost.com/saved-reports + cloudCostReports: + enabled: false # If true, overwrites report parameters set through UI + reports: + - title: "Cloud Cost Report 0" + window: "today" + aggregateBy: "service" + accumulate: false # daily resolution + # filters: + # - property: "service" + # value: "service1" # corresponds to a value to filter cloud cost aggregate by service data on. + + podAnnotations: {} + # iam.amazonaws.com/role: role-arn + + # Applies these labels to all Deployments, StatefulSets, DaemonSets, and their pod templates. + additionalLabels: {} + + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + fsGroup: 1001 + runAsGroup: 1001 + runAsUser: 1001 + fsGroupChangePolicy: OnRootMismatch + containerSecurityContext: + allowPrivilegeEscalation: false + privileged: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + + # Platforms is a higher-level abstraction for platform-specific values and settings. + platforms: + # Deploying to OpenShift (OCP) requires enabling this option. + openshift: + enabled: false # Deploy Kubecost to OpenShift. + route: + enabled: false # Create an OpenShift Route. + annotations: {} # Add annotations to the Route. + # host: kubecost.apps.okd4.example.com # Add a custom host for your Route. + # Create Security Context Constraint resources for the DaemonSets requiring additional privileges. + scc: + nodeExporter: false # Creates an SCC for Prometheus Node Exporter. This requires Node Exporter be enabled. + networkCosts: false # Creates an SCC for Kubecost network-costs. This requires network-costs be enabled. + # When OpenShift is enabled, the following securityContext will be applied to all resources unless they define their own. + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + # Set options for deploying with CI/CD tools like Argo CD. + cicd: + enabled: false # Set to true when using affected CI/CD tools for access to the below configuration options. + skipSanityChecks: false # If true, skip all sanity/existence checks for resources like Secrets. + + ## Kubecost Integrations + ## Ref: https://docs.kubecost.com/integrations + ## + integrations: + postgres: + enabled: false + runInterval: "12h" # How frequently to run the integration. + databaseHost: "" # REQUIRED. ex: my.postgres.database.azure.com + databasePort: "" # REQUIRED. ex: 5432 + databaseName: "" # REQUIRED. ex: postgres + databaseUser: "" # REQUIRED. ex: myusername + databasePassword: "" # REQUIRED. ex: mypassword + databaseSecretName: "" # OPTIONAL. Specify your own k8s secret containing the above credentials. Must have key "creds.json". + + ## Configure what Postgres table to write to, and what parameters to pass + ## when querying Kubecost's APIs. Ensure all parameters are enclosed in + ## quotes. Ref: https://docs.kubecost.com/apis/apis-overview + queryConfigs: + allocations: [] + # - databaseTable: "kubecost_allocation_data" + # window: "7d" + # aggregate: "namespace" + # idle: "true" + # shareIdle: "true" + # shareNamespaces: "kubecost,kube-system" + # shareLabels: "" + # - databaseTable: "kubecost_allocation_data_by_cluster" + # window: "10d" + # aggregate: "cluster" + # idle: "true" + # shareIdle: "false" + # shareNamespaces: "" + # shareLabels: "" + assets: [] + # - databaseTable: "kubecost_assets_data" + # window: "7d" + # aggregate: "cluster" + cloudCosts: [] + # - databaseTable: "kubecost_cloudcosts_data" + # window: "7d" + # aggregate: "service" + +## Provide a name override for the chart. +# nameOverride: "" +## Provide a full name override option for the chart. +# fullnameOverride: "" + +## This flag is only required for users upgrading to a new version of Kubecost. +## The flag is used to ensure users are aware of important +## (potentially breaking) changes included in the new version. +## +upgrade: + toV2: false + +# generated at http://kubecost.com/install, used for alerts tracking and free trials +kubecostToken: # "" + +# Advanced pipeline for custom prices, enterprise key required +pricingCsv: + enabled: false + location: + provider: "AWS" + region: "us-east-1" + URI: s3://kc-csv-test/pricing_schema.csv # a valid file URI + csvAccessCredentials: pricing-schema-access-secret + +# SAML integration for user management and RBAC, enterprise key required +# Ref: https://github.com/kubecost/docs/blob/main/user-management.md +saml: + enabled: false + # secretName: "kubecost-authzero" + # metadataSecretName: "kubecost-authzero-metadata" # One of metadataSecretName or idpMetadataURL must be set. defaults to metadataURL if set + # idpMetadataURL: "https://dev-elu2z98r.auth0.com/samlp/metadata/c6nY4M37rBP0qSO1IYIqBPPyIPxLS8v2" + # appRootURL: "http://localhost:9090" # sample URL + # authTimeout: 1440 # number of minutes the JWT will be valid + # redirectURL: "https://dev-elu2z98r.auth0.com/v2/logout" # callback URL redirected to after logout + # audienceURI: "http://localhost:9090" # by convention, the same as the appRootURL, but any string uniquely identifying kubecost to your samp IDP. Optional if you follow the convention + # nameIDFormat: "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" If your SAML provider requires a specific nameid format + # isGLUUProvider: false # An additional URL parameter must be appended for GLUU providers + # encryptionCertSecret: "kubecost-saml-cert" # k8s secret where the x509 certificate used to encrypt an Okta saml response is stored + # decryptionKeySecret: "kubecost-sank-decryption-key" # k8s secret where the private key associated with the encryptionCertSecret is stored + # authSecret: "random-string" # value of SAML secret used to issue tokens, will be autogenerated as random string if not provided + # authSecretName: "kubecost-saml-secret" # name of k8s secret where the authSecret will be stored, defaults to "kubecost-saml-secret" if not provided + rbac: + enabled: false + # groups: + # - name: admin + # enabled: false # if admin is disabled, all SAML users will be able to make configuration changes to the kubecost frontend + # assertionName: "http://schemas.auth0.com/userType" # a SAML Assertion, one of whose elements has a value that matches on of the values in assertionValues + # assertionValues: + # - "admin" + # - "superusers" + # - name: readonly + # enabled: false # if readonly is disabled, all users authorized on SAML will default to readonly + # assertionName: "http://schemas.auth0.com/userType" + # assertionValues: + # - "readonly" + # - name: editor + # enabled: true # if editor is enabled, editors will be allowed to edit reports/alerts scoped to them, and act as readers otherwise. Users will never default to editor. + # assertionName: "http://schemas.auth0.com/userType" + # assertionValues: + # - "editor" + +oidc: + enabled: false + clientID: "" # application/client client_id parameter obtained from provider, used to make requests to server + clientSecret: "" # application/client client_secret parameter obtained from provider, used to make requests to server + # secretName: "kubecost-oidc-secret" # k8s secret where clientsecret will be stored + # For use to provide a custom OIDC Secret. Overrides the usage of oidc.clientSecret and oidc.secretName. + # Should contain the field directly. + # Can be created using raw k8s secrets, external secrets, sealed secrets, or any other method. + existingCustomSecret: + enabled: false + name: "" # name of the secret containing the client secret + + # authURL: "https://my.auth.server/authorize" # endpoint for login to auth server + # loginRedirectURL: "http://my.kubecost.url/model/oidc/authorize" # Kubecost url configured in provider for redirect after authentication + # discoveryURL: "https://my.auth.server/.well-known/openid-configuration" # url for OIDC endpoint discovery + skipOnlineTokenValidation: false # if true, will skip accessing OIDC introspection endpoint for online token verification, and instead try to locally validate JWT claims + # hostedDomain: "example.com" # optional, blocks access to the auth domain specified in the hd claim of the provider ID token + rbac: + enabled: false + # groups: + # - name: admin + # enabled: false # if admin is disabled, all authenticated users will be able to make configuration changes to the kubecost frontend + # claimName: "roles" # Kubecost matches this string against the JWT's payload key containing RBAC info (this value is unique across identity providers) + # claimValues: # Kubecost matches these strings with the roles created in your identity provider + # - "admin" + # - "superusers" + # - name: readonly + # enabled: false # if readonly is disabled, all authenticated users will default to readonly + # claimName: "roles" + # claimValues: + # - "readonly" + # - name: editor + # enabled: false # if editor is enabled, editors will be allowed to edit reports/alerts scoped to them, and act as readers otherwise. Users will never default to editor. + # claimName: "roles" + # claimValues: + # - "editor" + +## Adds the HTTP_PROXY, HTTPS_PROXY, and NO_PROXY environment variables to all +## containers. Typically used in environments that have firewall rules which +## prevent kubecost from accessing cloud provider resources. +## Ref: https://www.oreilly.com/library/view/security-with-go/9781788627917/5ea6a02b-3d96-44b1-ad3c-6ab60fcbbe4f.xhtml +## +systemProxy: + enabled: false + httpProxyUrl: "" + httpsProxyUrl: "" + noProxy: "" + +# imagePullSecrets: +# - name: "image-pull-secret" + +# imageVersion uses the base image name (image:) but overrides the version +# pulled. It should be avoided. If non-default behavior is needed, use +# fullImageName for the relevant component. +# imageVersion: + +kubecostFrontend: + enabled: true + deployMethod: singlepod # haMode or singlepod - haMode is currently only supported with Enterprise tier + haReplicas: 2 # only used with haMode + image: "gcr.io/kubecost1/frontend" + imagePullPolicy: IfNotPresent + # fullImageName overrides the default image construction logic. The exact + # image provided (registry, image, tag) will be used for the frontend. + # fullImageName: + + # extraEnv: + # - name: NGINX_ENTRYPOINT_WORKER_PROCESSES_AUTOTUNE + # value: "1" + # securityContext: + # readOnlyRootFilesystem: true + resources: + requests: + cpu: "10m" + memory: "55Mi" + # limits: + # cpu: "100m" + # memory: "256Mi" + deploymentStrategy: {} + # rollingUpdate: + # maxSurge: 1 + # maxUnavailable: 1 + # type: RollingUpdate + + # Define a readiness probe for the Kubecost frontend container. + readinessProbe: + enabled: true + initialDelaySeconds: 1 + periodSeconds: 5 + failureThreshold: 6 + + # Define a liveness probe for the Kubecost frontend container. + livenessProbe: + enabled: true + initialDelaySeconds: 1 + periodSeconds: 5 + failureThreshold: 6 + ipv6: + enabled: true # disable if the cluster does not support ipv6 + # timeoutSeconds: 600 # should be rarely used, but can be increased if needed + # allow customizing nginx-conf server block + # extraServerConfig: |- + # proxy_busy_buffers_size 512k; + # proxy_buffers 4 512k; + # proxy_buffer_size 256k; + # large_client_header_buffers 4 64k; + # hideDiagnostics: false # useful if the primary is not monitored. Supported in limited environments. + # hideOrphanedResources: false # OrphanedResources works on the primary-cluster's cloud-provider only. + + # set to true to set all upstreams to use ..svc.cluster.local instead of just . + useDefaultFqdn: false +# api: +# fqdn: kubecost-api.kubecost.svc.cluster.local:9001 +# model: +# fqdn: kubecost-model.kubecost.svc.cluster.local:9003 +# forecasting: +# fqdn: kubecost-forcasting.kubecost.svc.cluster.local:5000 +# aggregator: +# fqdn: kubecost-aggregator.kubecost.svc.cluster.local:9004 +# cloudCost: +# fqdn: kubecost-cloud-cost.kubecost.svc.cluster.local:9005 +# multiClusterDiagnostics: +# fqdn: kubecost-multi-diag.kubecost.svc.cluster.local:9007 +# clusterController: +# fqdn: cluster-controller.kubecost.svc.cluster.local:9731 + +# Kubecost Metrics deploys a separate pod which will emit kubernetes specific metrics required +# by the cost-model. This pod is designed to remain active and decoupled from the cost-model itself. +# However, disabling this service/pod deployment will flag the cost-model to emit the metrics instead. +kubecostMetrics: + # emitPodAnnotations: false + # emitNamespaceAnnotations: false + # emitKsmV1Metrics: true # emit all KSM metrics in KSM v1. + # emitKsmV1MetricsOnly: false # emit only the KSM metrics missing from KSM v2. Advanced users only. + + # Optional + # The metrics exporter is a separate deployment and service (for prometheus scrape auto-discovery) + # which emits metrics cost-model relies on. Enabling this deployment also removes the KSM dependency + # from the cost-model. If the deployment is not enabled, the metrics will continue to be emitted from + # the cost-model. + exporter: + enabled: false + port: 9005 + # Adds the default Prometheus scrape annotations to the metrics exporter service. + # Set to false and use service.annotations (below) to set custom scrape annotations. + prometheusScrape: true + resources: {} + # requests: + # cpu: "200m" + # memory: "55Mi" + ## Node tolerations for server scheduling to nodes with taints + ## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + tolerations: [] + + # - key: "key" + # operator: "Equal|Exists" + # value: "value" + # effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" + affinity: {} + + service: + annotations: {} + + # Service Monitor for Kubecost Metrics + serviceMonitor: # the kubecost included prometheus uses scrapeConfigs and does not support service monitors. The following options assume an existing prometheus that supports serviceMonitors. + enabled: false + additionalLabels: {} + metricRelabelings: [] + relabelings: [] + ## PriorityClassName + ## Ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass + priorityClassName: "" + additionalLabels: {} + nodeSelector: {} + extraArgs: [] + +sigV4Proxy: + image: public.ecr.aws/aws-observability/aws-sigv4-proxy:latest + imagePullPolicy: IfNotPresent + name: aps + port: 8005 + region: us-west-2 # The AWS region + host: aps-workspaces.us-west-2.amazonaws.com # The hostname for AMP service. + # role_arn: arn:aws:iam:::role/role-name # The AWS IAM role to assume. + extraEnv: # Pass extra env variables to sigV4Proxy + # - name: AWS_ACCESS_KEY_ID + # value: + # - name: AWS_SECRET_ACCESS_KEY + # value: + # Optional resource requests and limits for the sigV4proxy container. + resources: {} + +kubecostModel: + image: "gcr.io/kubecost1/cost-model" + imagePullPolicy: IfNotPresent + # fullImageName overrides the default image construction logic. The exact + # image provided (registry, image, tag) will be used for cost-model. + # fullImageName: + + # extraEnv: + # - name: SOME_VARIABLE + # value: "some_value" + # securityContext: + # readOnlyRootFilesystem: true + # Build local cost allocation cache + warmCache: false + # Run allocation ETL pipelines + etl: true + # Enable the ETL filestore backing storage + etlFileStoreEnabled: true + # The total number of days the ETL pipelines will build + # Set to 0 to disable daily ETL (not recommended) + etlDailyStoreDurationDays: 91 + # The total number of hours the ETL pipelines will build + # Set to 0 to disable hourly ETL (not recommended) + # Must be < prometheus server retention, otherwise empty data may overwrite + # known-good data + etlHourlyStoreDurationHours: 49 + # The total number of weeks the ETL pipelines will build + # Set to 0 to disable weekly ETL (not recommended) + # The default is 53 to ensure at least a year of coverage (371 days) + etlWeeklyStoreDurationWeeks: 53 + # For deploying kubecost in a cluster that does not self-monitor + etlReadOnlyMode: false + + # The name of the Secret containing a bucket config for ETL backup. + # etlBucketConfigSecret: + # The name of the Secret containing a bucket config for Federated storage. The contents should be stored + # under a key named federated-store.yaml. + # federatedStorageConfigSecret: "" + + # Installs Kubecost/OpenCost plugins + plugins: + enabled: false + install: + enabled: false + fullImageName: curlimages/curl:latest + securityContext: + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1001 + folder: /opt/opencost/plugin + + # leave this commented to always download most recent version of plugins + # version: + + # the list of enabled plugins + enabledPlugins: [] + # - datadog + + # pre-existing secret for plugin configuration + existingCustomSecret: + enabled: false + name: "" # name of the secret containing plugin config + + secretName: kubecost-plugin-secret + + # uncomment this to define plugin configuration via the values file + # configs: + # datadog: | + # { + # "datadog_site": "", + # "datadog_api_key": "", + # "datadog_app_key": "" + # } + + allocation: + # Enables or disables adding node labels to allocation data (i.e. workloads). + # Defaults to "true" and starts with a sensible includeList for basics like + # topology (e.g. zone, region) and instance type labels. + # nodeLabels: + # enabled: true + # includeList: "node.kubernetes.io/instance-type,topology.kubernetes.io/region,topology.kubernetes.io/zone" + + # Enables or disables the ContainerStats pipeline, used for quantile-based + # queries like for request sizing recommendations. + # ContainerStats provides support for quantile-based request right-sizing + # recommendations. + # + # It is disabled by default to avoid problems in extremely high-scale Thanos + # environments. If you would like to try quantile-based request-sizing + # recommendations, enable this! If you are in a high-scale environment, + # please monitor Kubecost logs, Thanos query logs, and Thanos load closely. + # We hope to make major improvements at scale here soon! + # + containerStatsEnabled: true # enabled by default as of v2.2.0 + + # max number of concurrent Prometheus queries + maxQueryConcurrency: 5 + resources: + requests: + cpu: "200m" + memory: "55Mi" + # limits: + # cpu: "800m" + # memory: "256Mi" + + # Define a readiness probe for the Kubecost cost-model container. + readinessProbe: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 10 + failureThreshold: 200 + + # Define a liveness probe for the Kubecost cost-model container. + livenessProbe: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 10 + failureThreshold: 200 + extraArgs: [] + + # Optional. A list of extra environment variables to be added to the cost-model container. + # extraEnv: [] + # - name: LOG_LEVEL + # value: trace + # - name: LOG_FORMAT + # value: json + + # creates an ingress directly to the model container, for API access + ingress: + enabled: false + # className: nginx + labels: + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + annotations: + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + paths: ["/"] + pathType: ImplementationSpecific + hosts: + - cost-analyzer-model.local + tls: [] + # - secretName: cost-analyzer-model-tls + # hosts: + # - cost-analyzer-model.local + utcOffset: "+00:00" + # Optional - add extra ports to the cost-model container. For kubecost development purposes only - not recommended for users. + extraPorts: [] + # - name: debug + # port: 40000 + # targetPort: 40000 + # containerPort: 40000 + +# etlUtils is a utility currently used by Kubecost internal support to implement specific functionality related to Thanos conversion. +etlUtils: + enabled: false + fullImageName: null + resources: {} + env: {} + nodeSelector: {} + tolerations: [] + affinity: {} + +# Basic Kubecost ingress, more examples available at https://docs.kubecost.com/install-and-configure/install/ingress-examples +ingress: + enabled: false + # className: nginx + labels: + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + annotations: + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + paths: ["/"] # There's no need to route specifically to the pods-- we have an nginx deployed that handles routing + pathType: ImplementationSpecific + hosts: + - cost-analyzer.local + tls: [] + # - secretName: cost-analyzer-tls + # hosts: + # - cost-analyzer.local + +nodeSelector: {} + +tolerations: [] +# - key: "key" +# operator: "Equal|Exists" +# value: "value" +# effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" + +affinity: {} + +topologySpreadConstraints: [] + +# If true, creates a PriorityClass to be used by the cost-analyzer pod +priority: + enabled: false + name: "" # Provide name of existing priority class only. If left blank, upstream chart will create one from default template. + +# If true, enable creation of NetworkPolicy resources. +networkPolicy: + enabled: false + denyEgress: true # create a network policy that denies egress from kubecost + sameNamespace: true # Set to true if cost analyzer and prometheus are on the same namespace +# namespace: kubecost # Namespace where prometheus is installed + + # Cost-analyzer specific vars using the new template + costAnalyzer: + enabled: false # If true, create a network policy for cost-analyzer + annotations: {} # annotations to be added to the network policy + additionalLabels: {} # additional labels to be added to the network policy + # Examples rules: + # ingressRules: + # - selectors: # allow ingress from self on all ports + # - podSelector: + # matchLabels: + # app.kubernetes.io/name: cost-analyzer + # - selectors: # allow egress access to prometheus + # - namespaceSelector: + # matchLabels: + # name: prometheus + # podSelector: + # matchLabels: + # app: prometheus + # ports: + # - protocol: TCP + # port: 9090 + # egressRules: + # - selectors: # restrict egress to inside cluster + # - namespaceSelector: {} + +## @param extraVolumes A list of volumes to be added to the pod +## +extraVolumes: [] +## @param extraVolumeMounts A list of volume mounts to be added to the pod +## +extraVolumeMounts: [] + +# Define persistence volume for cost-analyzer, more information at https://docs.kubecost.com/install-and-configure/install/storage +persistentVolume: + size: 32Gi + dbSize: 32.0Gi + enabled: true # Note that setting this to false means configurations will be wiped out on pod restart. + # storageClass: "-" # + # existingClaim: kubecost-cost-analyzer # a claim in the same namespace as kubecost + labels: {} + annotations: {} + # helm.sh/resource-policy: keep # https://helm.sh/docs/howto/charts_tips_and_tricks/#tell-helm-not-to-uninstall-a-resource + + # Enables a separate PV specifically for ETL data. This should be avoided, but + # is kept for legacy compatibility. + dbPVEnabled: false + +service: + type: ClusterIP + port: 9090 + targetPort: 9090 + nodePort: {} + labels: {} + annotations: {} + # loadBalancerSourceRanges: [] + sessionAffinity: + enabled: false # Makes sure that connections from a client are passed to the same Pod each time, when set to `true`. You should set it when you enabled authentication through OIDC or SAML integration. + timeoutSeconds: 10800 + +prometheus: + ## Provide a full name override for Prometheus. + # fullnameOverride: "" + ## Provide a name override for Prometheus. + # nameOverride: "" + + rbac: + create: true # Create the RBAC resources for Prometheus. + + ## Define serviceAccount names for components. Defaults to component's fully qualified name. + ## + serviceAccounts: + alertmanager: + create: true + name: + nodeExporter: + create: true + name: + pushgateway: + create: true + name: + server: + create: true + name: + ## Prometheus server ServiceAccount annotations. + ## Can be used for AWS IRSA annotations when using Remote Write mode with Amazon Managed Prometheus. + annotations: {} + + ## Specify an existing ConfigMap to be used by Prometheus when using self-signed certificates. + ## + # selfsignedCertConfigMapName: "" + + imagePullSecrets: + # - name: "image-pull-secret" + + extraScrapeConfigs: | + - job_name: kubecost + honor_labels: true + scrape_interval: 1m + scrape_timeout: 60s + metrics_path: /metrics + scheme: http + dns_sd_configs: + - names: + - {{ template "cost-analyzer.serviceName" . }} + type: 'A' + port: 9003 + - job_name: kubecost-networking + kubernetes_sd_configs: + - role: pod + relabel_configs: + # Scrape only the the targets matching the following metadata + - source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_instance] + action: keep + regex: kubecost + - source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_name] + action: keep + regex: network-costs + - job_name: kubecost-aggregator + scrape_interval: 1m + scrape_timeout: 60s + metrics_path: /metrics + scheme: http + dns_sd_configs: + - names: + - {{ template "aggregator.serviceName" . }} + type: 'A' + {{- if or .Values.saml.enabled .Values.oidc.enabled }} + port: 9008 + {{- else }} + port: 9004 + {{- end }} + server: + # If clusterIDConfigmap is defined, instead use user-generated configmap with key CLUSTER_ID + # to use as unique cluster ID in kubecost cost-analyzer deployment. + # This overrides the cluster_id set in prometheus.server.global.external_labels. + # NOTE: This does not affect the external_labels set in prometheus config. + # clusterIDConfigmap: cluster-id-configmap + + ## Provide a full name override for the Prometheus server. + # fullnameOverride: "" + + ## Prometheus server container name + ## + enabled: true + name: server + sidecarContainers: + strategy: + type: Recreate + rollingUpdate: null + + ## Prometheus server container image + ## + image: + repository: quay.io/prometheus/prometheus + tag: v2.52.0 + pullPolicy: IfNotPresent + + ## prometheus server priorityClassName + ## + priorityClassName: "" + + ## The URL prefix at which the container can be accessed. Useful in the case the '-web.external-url' includes a slug + ## so that the various internal URLs are still able to access as they are in the default case. + ## (Optional) + prefixURL: "" + + ## External URL which can access alertmanager + ## Maybe same with Ingress host name + baseURL: "" + + ## Additional server container environment variables + ## + ## You specify this manually like you would a raw deployment manifest. + ## This means you can bind in environment variables from secrets. + ## + ## e.g. static environment variable: + ## - name: DEMO_GREETING + ## value: "Hello from the environment" + ## + ## e.g. secret environment variable: + ## - name: USERNAME + ## valueFrom: + ## secretKeyRef: + ## name: mysecret + ## key: username + env: [] + + extraFlags: + - web.enable-lifecycle + ## web.enable-admin-api flag controls access to the administrative HTTP API which includes functionality such as + ## deleting time series. This is disabled by default. + # - web.enable-admin-api + ## + ## storage.tsdb.no-lockfile flag controls BD locking + # - storage.tsdb.no-lockfile + ## + ## storage.tsdb.wal-compression flag enables compression of the write-ahead log (WAL) + # - storage.tsdb.wal-compression + + ## Path to a configuration file on prometheus server container FS + configPath: /etc/config/prometheus.yml + + global: + ## How frequently to scrape targets by default + ## + scrape_interval: 1m + ## How long until a scrape request times out + ## + scrape_timeout: 60s + ## How frequently to evaluate rules + ## + evaluation_interval: 1m + external_labels: + cluster_id: cluster-one # Each cluster should have a unique ID + ## https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write + ## + remoteWrite: {} + ## https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_read + ## + remoteRead: {} + + ## Additional Prometheus server container arguments + ## + extraArgs: + query.max-concurrency: 1 + query.max-samples: 100000000 + + ## Additional InitContainers to initialize the pod + ## + extraInitContainers: [] + + ## Additional Prometheus server Volume mounts + ## + extraVolumeMounts: [] + + ## Additional Prometheus server Volumes + ## + extraVolumes: [] + + ## Additional Prometheus server hostPath mounts + ## + extraHostPathMounts: [] + # - name: certs-dir + # mountPath: /etc/kubernetes/certs + # subPath: "" + # hostPath: /etc/kubernetes/certs + # readOnly: true + + extraConfigmapMounts: [] + # - name: certs-configmap + # mountPath: /prometheus + # subPath: "" + # configMap: certs-configmap + # readOnly: true + + ## Additional Prometheus server Secret mounts + # Defines additional mounts with secrets. Secrets must be manually created in the namespace. + extraSecretMounts: [] + # - name: secret-files + # mountPath: /etc/secrets + # subPath: "" + # secretName: prom-secret-files + # readOnly: true + + ## ConfigMap override where fullname is {{.Release.Name}}-{{.Values.server.configMapOverrideName}} + ## Defining configMapOverrideName will cause templates/server-configmap.yaml + ## to NOT generate a ConfigMap resource + ## + configMapOverrideName: "" + + ingress: + ## If true, Prometheus server Ingress will be created + ## + enabled: false + # className: nginx + + ## Prometheus server Ingress annotations + ## + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: 'true' + + ## Prometheus server Ingress additional labels + ## + extraLabels: {} + + ## Prometheus server Ingress hostnames with optional path + ## Must be provided if Ingress is enabled + ## + hosts: [] + # - prometheus.domain.com + # - domain.com/prometheus + + ## PathType determines the interpretation of the Path matching + pathType: "Prefix" + + ## Extra paths to prepend to every host configuration. This is useful when working with annotation based services. + extraPaths: [] + # - path: /* + # backend: + # serviceName: ssl-redirect + # servicePort: use-annotation + + ## Prometheus server Ingress TLS configuration + ## Secrets must be manually created in the namespace + ## + tls: [] + # - secretName: prometheus-server-tls + # hosts: + # - prometheus.domain.com + + ## Server Deployment Strategy type + # strategy: + # type: Recreate + + ## Node tolerations for server scheduling to nodes with taints + ## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + ## + tolerations: [] + # - key: "key" + # operator: "Equal|Exists" + # value: "value" + # effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" + + ## Node labels for Prometheus server pod assignment + ## Ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + + ## Pod affinity + ## + affinity: {} + + ## PodDisruptionBudget settings + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ + ## + podDisruptionBudget: + enabled: false + maxUnavailable: 1 + + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + persistentVolume: + ## If true, Prometheus server will create/use a Persistent Volume Claim + ## If false, use emptyDir + ## + enabled: true + + ## Prometheus server data Persistent Volume access modes + ## Must match those of existing PV or dynamic provisioner + ## Ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + accessModes: + - ReadWriteOnce + + ## Prometheus server data Persistent Volume annotations + ## + annotations: {} + # helm.sh/resource-policy: keep # https://helm.sh/docs/howto/charts_tips_and_tricks/#tell-helm-not-to-uninstall-a-resource + + ## Prometheus server data Persistent Volume existing claim name + ## Requires server.persistentVolume.enabled: true + ## If defined, PVC must be created manually before volume will be bound + existingClaim: "" + + ## Prometheus server data Persistent Volume mount root path + ## + mountPath: /data + + ## Prometheus server data Persistent Volume size + ## + size: 32Gi + + ## Prometheus server data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + + ## Prometheus server data Persistent Volume Binding Mode + ## If defined, volumeBindingMode: + ## If undefined (the default) or set to null, no volumeBindingMode spec is + ## set, choosing the default mode. + ## + # volumeBindingMode: "" + + ## Subdirectory of Prometheus server data Persistent Volume to mount + ## Useful if the volume's root directory is not empty + ## + subPath: "" + + emptyDir: + sizeLimit: "" + + ## Annotations to be added to Prometheus server pods + ## + podAnnotations: {} + # iam.amazonaws.com/role: prometheus + + ## Annotations to be added to the Prometheus Server deployment + ## + deploymentAnnotations: {} + + ## Labels to be added to Prometheus server pods + ## + podLabels: {} + + ## Prometheus AlertManager configuration + ## + alertmanagers: [] + + ## Use a StatefulSet if replicaCount needs to be greater than 1 (see below) + ## + replicaCount: 1 + + statefulSet: + ## If true, use a statefulset instead of a deployment for pod management. + ## This allows to scale replicas to more than 1 pod + ## + enabled: false + + annotations: {} + labels: {} + podManagementPolicy: OrderedReady + + ## Alertmanager headless service to use for the statefulset + ## + headless: + annotations: {} + labels: {} + servicePort: 80 + + ## Prometheus server readiness and liveness probe initial delay and timeout + ## Ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ + ## + readinessProbeInitialDelay: 5 + readinessProbeTimeout: 3 + readinessProbeFailureThreshold: 3 + readinessProbeSuccessThreshold: 1 + livenessProbeInitialDelay: 5 + livenessProbeTimeout: 3 + livenessProbeFailureThreshold: 3 + livenessProbeSuccessThreshold: 1 + + ## Prometheus server resource requests and limits + ## Ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: {} + # limits: + # cpu: 500m + # memory: 512Mi + # requests: + # cpu: 500m + # memory: 512Mi + + ## Vertical Pod Autoscaler config + ## Ref: https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler + verticalAutoscaler: + ## If true a VPA object will be created for the controller (either StatefulSet or Deployment, based on above configs) + enabled: false + ## Optional. Defaults to "Auto" if not specified. + # updateMode: "Auto" + ## Mandatory. Without, VPA will not be created. + # containerPolicies: + # - containerName: 'prometheus-server' + + ## Security context to be added to server pods + ## + securityContext: {} + # runAsUser: 1001 + # runAsNonRoot: true + # runAsGroup: 1001 + # fsGroup: 1001 + + containerSecurityContext: {} + + service: + annotations: {} + labels: {} + clusterIP: "" + # nodePort: "" + + ## List of IP addresses at which the Prometheus server service is available + ## Ref: https://kubernetes.io/docs/user-guide/services/#external-ips + ## + externalIPs: [] + + loadBalancerIP: "" + loadBalancerSourceRanges: [] + servicePort: 80 + sessionAffinity: None + type: ClusterIP + + ## Enable gRPC port on service to allow auto discovery with thanos-querier + gRPC: + enabled: false + servicePort: 10901 + # nodePort: 10901 + + ## If using a statefulSet (statefulSet.enabled=true), configure the + ## service to connect to a specific replica to have a consistent view + ## of the data. + statefulsetReplica: + enabled: false + replica: 0 + + ## Prometheus server pod termination grace period + ## + terminationGracePeriodSeconds: 300 + + ## Prometheus data retention period (default if not specified is 97 hours) + ## + ## Kubecost builds up its own persistent store of metric data on the + ## filesystem (usually a PV) and, when using ETL Backup and/or Federated + ## ETL, in more durable object storage like S3 or GCS. Kubecost's data + ## retention is _not_ tied to the configured Prometheus retention. + ## + ## For data durability, we recommend using ETL Backup instead of relying on + ## Prometheus retention. + ## + ## Lower retention values will affect Prometheus by reducing resource + ## consumption and increasing stability. It _must not_ be set below or equal + ## to kubecostModel.etlHourlyStoreDurationHours, otherwise empty data sets + ## may overwrite good data sets. For now, it must also be >= 49h for Daily + ## ETL stability. + ## + ## "ETL Rebuild" and "ETL Repair" is only possible on data available within + ## this retention window. This is an extremely rare operation. + ## + ## If you want maximum security in the event of a Kubecost agent + ## (cost-model) outage, increase this value. The current default of 97h is + ## intended to balance Prometheus stability and resource consumption + ## against the event of an outage in Kubecost which would necessitate a + ## version change. 4 days should provide enough time for most users to + ## notice a problem and initiate corrective action. + retention: 97h + # retentionSize: should be significantly greater than the storage used in the number of hours set in etlHourlyStoreDurationHours + + # Install Prometheus Alert Manager + alertmanager: + ## If false, alertmanager will not be installed + ## + enabled: false + + ## Provide a full name override for Prometheus alertmanager. + # fullnameOverride: "" + + strategy: + type: Recreate + rollingUpdate: null + + ## alertmanager container name + ## + name: alertmanager + + ## alertmanager container image + ## + image: + repository: quay.io/prometheus/alertmanager + tag: v0.27.0 + pullPolicy: IfNotPresent + + ## alertmanager priorityClassName + ## + priorityClassName: "" + + ## Additional alertmanager container arguments + ## + extraArgs: {} + + ## The URL prefix at which the container can be accessed. Useful in the case the '-web.external-url' includes a slug + ## so that the various internal URLs are still able to access as they are in the default case. + ## (Optional) + prefixURL: "" + + ## External URL which can access alertmanager + baseURL: "http://localhost:9093" + + ## Additional alertmanager container environment variable + ## For instance to add a http_proxy + ## + extraEnv: {} + + ## Additional alertmanager Secret mounts + # Defines additional mounts with secrets. Secrets must be manually created in the namespace. + extraSecretMounts: [] + # - name: secret-files + # mountPath: /etc/secrets + # subPath: "" + # secretName: alertmanager-secret-files + # readOnly: true + + ## ConfigMap override where fullname is {{.Release.Name}}-{{.Values.alertmanager.configMapOverrideName}} + ## Defining configMapOverrideName will cause templates/alertmanager-configmap.yaml + ## to NOT generate a ConfigMap resource + ## + configMapOverrideName: "" + + ## The name of a secret in the same kubernetes namespace which contains the Alertmanager config + ## Defining configFromSecret will cause templates/alertmanager-configmap.yaml + ## to NOT generate a ConfigMap resource + ## + configFromSecret: "" + + ## The configuration file name to be loaded to alertmanager + ## Must match the key within configuration loaded from ConfigMap/Secret + ## + configFileName: alertmanager.yml + + ingress: + ## If true, alertmanager Ingress will be created + ## + enabled: false + + ## alertmanager Ingress annotations + ## + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: 'true' + + ## alertmanager Ingress additional labels + ## + extraLabels: {} + + ## alertmanager Ingress hostnames with optional path + ## Must be provided if Ingress is enabled + ## + hosts: [] + # - alertmanager.domain.com + # - domain.com/alertmanager + + ## Extra paths to prepend to every host configuration. This is useful when working with annotation based services. + extraPaths: [] + # - path: /* + # backend: + # serviceName: ssl-redirect + # servicePort: use-annotation + + ## alertmanager Ingress TLS configuration + ## Secrets must be manually created in the namespace + ## + tls: [] + # - secretName: prometheus-alerts-tls + # hosts: + # - alertmanager.domain.com + + ## Alertmanager Deployment Strategy type + # strategy: + # type: Recreate + + ## Node tolerations for alertmanager scheduling to nodes with taints + ## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + ## + tolerations: [] + # - key: "key" + # operator: "Equal|Exists" + # value: "value" + # effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" + + ## Node labels for alertmanager pod assignment + ## Ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + + ## Pod affinity + ## + affinity: {} + + ## PodDisruptionBudget settings + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ + ## + podDisruptionBudget: + enabled: false + maxUnavailable: 1 + + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + persistentVolume: + ## If true, alertmanager will create/use a Persistent Volume Claim + ## If false, use emptyDir + ## + enabled: true + + ## alertmanager data Persistent Volume access modes + ## Must match those of existing PV or dynamic provisioner + ## Ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + accessModes: + - ReadWriteOnce + + ## alertmanager data Persistent Volume Claim annotations + ## + annotations: {} + + ## alertmanager data Persistent Volume existing claim name + ## Requires alertmanager.persistentVolume.enabled: true + ## If defined, PVC must be created manually before volume will be bound + existingClaim: "" + + ## alertmanager data Persistent Volume mount root path + ## + mountPath: /data + + ## alertmanager data Persistent Volume size + ## + size: 2Gi + + ## alertmanager data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + + ## alertmanager data Persistent Volume Binding Mode + ## If defined, volumeBindingMode: + ## If undefined (the default) or set to null, no volumeBindingMode spec is + ## set, choosing the default mode. + ## + # volumeBindingMode: "" + + ## Subdirectory of alertmanager data Persistent Volume to mount + ## Useful if the volume's root directory is not empty + ## + subPath: "" + + ## Annotations to be added to alertmanager pods + ## + podAnnotations: {} + ## Tell prometheus to use a specific set of alertmanager pods + ## instead of all alertmanager pods found in the same namespace + ## Useful if you deploy multiple releases within the same namespace + ## + ## prometheus.io/probe: alertmanager-teamA + + ## Labels to be added to Prometheus AlertManager pods + ## + podLabels: {} + + ## Use a StatefulSet if replicaCount needs to be greater than 1 (see below) + ## + replicaCount: 1 + + statefulSet: + ## If true, use a statefulset instead of a deployment for pod management. + ## This allows to scale replicas to more than 1 pod + ## + enabled: false + + podManagementPolicy: OrderedReady + + ## Alertmanager headless service to use for the statefulset + ## + headless: + annotations: {} + labels: {} + + ## Enabling peer mesh service end points for enabling the HA alert manager + ## Ref: https://github.com/prometheus/alertmanager/blob/master/README.md + # enableMeshPeer : true + + servicePort: 80 + + ## alertmanager resource requests and limits + ## Ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: {} + # limits: + # cpu: 10m + # memory: 32Mi + # requests: + # cpu: 10m + # memory: 32Mi + + ## Security context to be added to alertmanager pods + ## + securityContext: + runAsUser: 1001 + runAsNonRoot: true + runAsGroup: 1001 + fsGroup: 1001 + + service: + annotations: {} + labels: {} + clusterIP: "" + + ## Enabling peer mesh service end points for enabling the HA alert manager + ## Ref: https://github.com/prometheus/alertmanager/blob/master/README.md + # enableMeshPeer : true + + ## List of IP addresses at which the alertmanager service is available + ## Ref: https://kubernetes.io/docs/user-guide/services/#external-ips + ## + externalIPs: [] + + loadBalancerIP: "" + loadBalancerSourceRanges: [] + servicePort: 80 + # nodePort: 30000 + sessionAffinity: None + type: ClusterIP + + # Define a custom scheduler for Alertmanager pods + # schedulerName: default-scheduler + + ## alertmanager ConfigMap entries + ## + alertmanagerFiles: + alertmanager.yml: + global: {} + # slack_api_url: '' + + receivers: + - name: default-receiver + # slack_configs: + # - channel: '@you' + # send_resolved: true + + route: + group_wait: 10s + group_interval: 5m + receiver: default-receiver + repeat_interval: 3h + + ## Monitors ConfigMap changes and POSTs to a URL + configmapReload: + prometheus: + ## If false, the configmap-reload container will not be deployed + ## + enabled: false + + ## configmap-reload container name + ## + name: configmap-reload + + ## configmap-reload container image + ## + image: + repository: quay.io/prometheus-operator/prometheus-config-reloader + tag: v0.74.0 + pullPolicy: IfNotPresent + + ## Additional configmap-reload container arguments + ## + extraArgs: {} + ## Additional configmap-reload volume directories + ## + extraVolumeDirs: [] + + ## Additional configmap-reload mounts + ## + extraConfigmapMounts: [] + # - name: prometheus-alerts + # mountPath: /etc/alerts.d + # subPath: "" + # configMap: prometheus-alerts + # readOnly: true + + ## configmap-reload resource requests and limits + ## Ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: {} + + ## configmap-reload container securityContext + containerSecurityContext: {} + + alertmanager: + ## If false, the configmap-reload container will not be deployed + ## + enabled: false + + ## configmap-reload container name + ## + name: configmap-reload + + ## configmap-reload container image + ## + image: + repository: quay.io/prometheus-operator/prometheus-config-reloader + tag: v0.74.0 + pullPolicy: IfNotPresent + + ## Additional configmap-reload container arguments + ## + extraArgs: {} + ## Additional configmap-reload volume directories + ## + extraVolumeDirs: [] + + + ## Additional configmap-reload mounts + ## + extraConfigmapMounts: [] + # - name: prometheus-alerts + # mountPath: /etc/alerts.d + # subPath: "" + # configMap: prometheus-alerts + # readOnly: true + + + ## configmap-reload resource requests and limits + ## Ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: {} + + # node-export must be disabled if there is an existing daemonset: https://guide.kubecost.com/hc/en-us/articles/4407601830679-Troubleshoot-Install#a-name-node-exporter-a-issue-failedscheduling-kubecost-prometheus-node-exporter + nodeExporter: + ## If false, node-exporter will not be installed. + ## This is disabled by default in Kubecost 2.0, though it can be enabled as needed. + ## + enabled: false + + ## Provide a full name override for node exporter. + # fullnameOverride: "" + + ## If true, node-exporter pods share the host network namespace + ## + hostNetwork: true + + ## If true, node-exporter pods share the host PID namespace + ## + hostPID: true + + ## node-exporter dns policy + ## + dnsPolicy: ClusterFirstWithHostNet + + ## node-exporter container name + ## + name: node-exporter + + ## node-exporter container image + ## + image: + repository: prom/node-exporter + tag: v1.8.0 + pullPolicy: IfNotPresent + + ## node-exporter priorityClassName + ## + priorityClassName: "" + + ## Custom Update Strategy + ## + updateStrategy: + type: RollingUpdate + + ## Additional node-exporter container arguments + ## + extraArgs: {} + + ## Additional node-exporter hostPath mounts + ## + extraHostPathMounts: [] + # - name: textfile-dir + # mountPath: /srv/txt_collector + # hostPath: /var/lib/node-exporter + # readOnly: true + # mountPropagation: HostToContainer + + extraConfigmapMounts: [] + # - name: certs-configmap + # mountPath: /prometheus + # configMap: certs-configmap + # readOnly: true + + ## Set a custom affinity for node-exporter + ## + # affinity: + + ## Node tolerations for node-exporter scheduling to nodes with taints + ## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + ## + tolerations: [] + # - key: "key" + # operator: "Equal|Exists" + # value: "value" + # effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" + + ## Node labels for node-exporter pod assignment + ## Ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + + ## Annotations to be added to node-exporter pods + ## + podAnnotations: {} + + ## Annotations to be added to the node-exporter DaemonSet + ## + deploymentAnnotations: {} + + ## Labels to be added to node-exporter pods + ## + pod: + labels: {} + + ## PodDisruptionBudget settings + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ + ## + podDisruptionBudget: + enabled: false + maxUnavailable: 1 + + ## node-exporter resource limits & requests + ## Ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: {} + # limits: + # cpu: 200m + # memory: 50Mi + # requests: + # cpu: 100m + # memory: 30Mi + + ## Security context to be added to node-exporter pods + ## + securityContext: {} + # runAsUser: 0 + + service: + annotations: + prometheus.io/scrape: "true" + labels: {} + + # Exposed as a headless service: + # https://kubernetes.io/docs/concepts/services-networking/service/#headless-services + clusterIP: None + + ## List of IP addresses at which the node-exporter service is available + ## Ref: https://kubernetes.io/docs/user-guide/services/#external-ips + ## + externalIPs: [] + + hostPort: 9100 + loadBalancerIP: "" + loadBalancerSourceRanges: [] + servicePort: 9100 + type: ClusterIP + + # Install Prometheus Push Gateway. + pushgateway: + ## If false, pushgateway will not be installed + ## + enabled: false + + ## Provide a full name override for Prometheus push gateway. + # fullnameOverride: "" + + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + ## pushgateway container name + ## + name: pushgateway + + ## pushgateway container image + ## + image: + repository: prom/pushgateway + tag: v1.8.0 + pullPolicy: IfNotPresent + + ## pushgateway priorityClassName + ## + priorityClassName: "" + + ## Additional pushgateway container arguments + ## + ## for example: persistence.file: /data/pushgateway.data + extraArgs: {} + + ingress: + ## If true, pushgateway Ingress will be created + ## + enabled: false + + ## pushgateway Ingress annotations + ## + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: 'true' + + ## pushgateway Ingress hostnames with optional path + ## Must be provided if Ingress is enabled + ## + hosts: [] + # - pushgateway.domain.com + # - domain.com/pushgateway + + ## Extra paths to prepend to every host configuration. This is useful when working with annotation based services. + extraPaths: [] + # - path: /* + # backend: + # serviceName: ssl-redirect + # servicePort: use-annotation + + ## pushgateway Ingress TLS configuration + ## Secrets must be manually created in the namespace + ## + tls: [] + # - secretName: prometheus-alerts-tls + # hosts: + # - pushgateway.domain.com + + ## Node tolerations for pushgateway scheduling to nodes with taints + ## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + ## + tolerations: [] + # - key: "key" + # operator: "Equal|Exists" + # value: "value" + # effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" + + ## Node labels for pushgateway pod assignment + ## Ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + + ## Annotations to be added to pushgateway pods + ## + podAnnotations: {} + + replicaCount: 1 + + ## PodDisruptionBudget settings + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ + ## + podDisruptionBudget: + enabled: false + maxUnavailable: 1 + + ## pushgateway resource requests and limits + ## Ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: {} + # limits: + # cpu: 10m + # memory: 32Mi + # requests: + # cpu: 10m + # memory: 32Mi + + ## Security context to be added to push-gateway pods + ## + securityContext: + runAsUser: 1001 + runAsNonRoot: true + + service: + annotations: + prometheus.io/probe: pushgateway + labels: {} + clusterIP: "" + + ## List of IP addresses at which the pushgateway service is available + ## Ref: https://kubernetes.io/docs/user-guide/services/#external-ips + ## + externalIPs: [] + + loadBalancerIP: "" + loadBalancerSourceRanges: [] + servicePort: 9091 + type: ClusterIP + + strategy: + type: Recreate + rollingUpdate: null + + + persistentVolume: + ## If true, pushgateway will create/use a Persistent Volume Claim + ## If false, use emptyDir + ## + enabled: true + + ## pushgateway data Persistent Volume access modes + ## Must match those of existing PV or dynamic provisioner + ## Ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + accessModes: + - ReadWriteOnce + + ## pushgateway data Persistent Volume Claim annotations + ## + annotations: {} + + ## pushgateway data Persistent Volume existing claim name + ## Requires pushgateway.persistentVolume.enabled: true + ## If defined, PVC must be created manually before volume will be bound + existingClaim: "" + + ## pushgateway data Persistent Volume mount root path + ## + mountPath: /data + + ## pushgateway data Persistent Volume size + ## + size: 2Gi + + ## pushgateway data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + + ## pushgateway data Persistent Volume Binding Mode + ## If defined, volumeBindingMode: + ## If undefined (the default) or set to null, no volumeBindingMode spec is + ## set, choosing the default mode. + ## + # volumeBindingMode: "" + + ## Subdirectory of pushgateway data Persistent Volume to mount + ## Useful if the volume's root directory is not empty + ## + subPath: "" + + serverFiles: + ## Alerts configuration + ## Ref: https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/ + alerting_rules.yml: {} + # groups: + # - name: Instances + # rules: + # - alert: InstanceDown + # expr: up == 0 + # for: 5m + # labels: + # severity: page + # annotations: + # description: '{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 5 minutes.' + # summary: 'Instance {{ $labels.instance }} down' + ## DEPRECATED DEFAULT VALUE, unless explicitly naming your files, please use alerting_rules.yml + alerts: {} + + ## Records configuration + ## Ref: https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/ + recording_rules.yml: {} + ## DEPRECATED DEFAULT VALUE, unless explicitly naming your files, please use recording_rules.yml + + prometheus.yml: + rule_files: + - /etc/config/recording_rules.yml + - /etc/config/alerting_rules.yml + ## Below two files are DEPRECATED will be removed from this default values file + - /etc/config/rules + - /etc/config/alerts + + scrape_configs: + - job_name: prometheus + static_configs: + - targets: + - localhost:9090 + + # A scrape configuration for running Prometheus on a Kubernetes cluster. + # This uses separate scrape configs for cluster components (i.e. API server, node) + # and services to allow each to use different authentication configs. + # + # Kubernetes labels will be added as Prometheus labels on metrics via the + # `labelmap` relabeling action. + + - job_name: 'kubernetes-nodes-cadvisor' + + # Default to scraping over https. If required, just disable this or change to + # `http`. + scheme: https + + # This TLS & bearer token file config is used to connect to the actual scrape + # endpoints for cluster components. This is separate to discovery auth + # configuration because discovery & scraping are two separate concerns in + # Prometheus. The discovery auth config is automatic if Prometheus runs inside + # the cluster. Otherwise, more config options have to be provided within the + # . + tls_config: + ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt + # If your node certificates are self-signed or use a different CA to the + # master CA, then disable certificate verification below. Note that + # certificate verification is an integral part of a secure infrastructure + # so this should only be disabled in a controlled environment. You can + # disable certificate verification by uncommenting the line below. + # + insecure_skip_verify: true + bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token + + kubernetes_sd_configs: + - role: node + + # This configuration will work only on kubelet 1.7.3+ + # As the scrape endpoints for cAdvisor have changed + # if you are using older version you need to change the replacement to + # replacement: /api/v1/nodes/$1:4194/proxy/metrics + # more info here https://github.com/coreos/prometheus-operator/issues/633 + relabel_configs: + - action: labelmap + regex: __meta_kubernetes_node_label_(.+) + - target_label: __address__ + replacement: kubernetes.default.svc:443 + - source_labels: [__meta_kubernetes_node_name] + regex: (.+) + target_label: __metrics_path__ + replacement: /api/v1/nodes/$1/proxy/metrics/cadvisor + + metric_relabel_configs: + - source_labels: [__name__] + regex: (container_cpu_usage_seconds_total|container_memory_working_set_bytes|container_network_receive_errors_total|container_network_transmit_errors_total|container_network_receive_packets_dropped_total|container_network_transmit_packets_dropped_total|container_memory_usage_bytes|container_cpu_cfs_throttled_periods_total|container_cpu_cfs_periods_total|container_fs_usage_bytes|container_fs_limit_bytes|container_cpu_cfs_periods_total|container_fs_inodes_free|container_fs_inodes_total|container_fs_usage_bytes|container_fs_limit_bytes|container_cpu_cfs_throttled_periods_total|container_cpu_cfs_periods_total|container_network_receive_bytes_total|container_network_transmit_bytes_total|container_fs_inodes_free|container_fs_inodes_total|container_fs_usage_bytes|container_fs_limit_bytes|container_spec_cpu_shares|container_spec_memory_limit_bytes|container_network_receive_bytes_total|container_network_transmit_bytes_total|container_fs_reads_bytes_total|container_network_receive_bytes_total|container_fs_writes_bytes_total|container_fs_reads_bytes_total|cadvisor_version_info|kubecost_pv_info) + action: keep + - source_labels: [container] + target_label: container_name + regex: (.+) + action: replace + - source_labels: [pod] + target_label: pod_name + regex: (.+) + action: replace + + # A scrape configuration for running Prometheus on a Kubernetes cluster. + # This uses separate scrape configs for cluster components (i.e. API server, node) + # and services to allow each to use different authentication configs. + # + # Kubernetes labels will be added as Prometheus labels on metrics via the + # `labelmap` relabeling action. + + - job_name: 'kubernetes-nodes' + + # Default to scraping over https. If required, just disable this or change to + # `http`. + scheme: https + + # This TLS & bearer token file config is used to connect to the actual scrape + # endpoints for cluster components. This is separate to discovery auth + # configuration because discovery & scraping are two separate concerns in + # Prometheus. The discovery auth config is automatic if Prometheus runs inside + # the cluster. Otherwise, more config options have to be provided within the + # . + tls_config: + ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt + # If your node certificates are self-signed or use a different CA to the + # master CA, then disable certificate verification below. Note that + # certificate verification is an integral part of a secure infrastructure + # so this should only be disabled in a controlled environment. You can + # disable certificate verification by uncommenting the line below. + # + insecure_skip_verify: true + bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token + + kubernetes_sd_configs: + - role: node + + relabel_configs: + - action: labelmap + regex: __meta_kubernetes_node_label_(.+) + - target_label: __address__ + replacement: kubernetes.default.svc:443 + - source_labels: [__meta_kubernetes_node_name] + regex: (.+) + target_label: __metrics_path__ + replacement: /api/v1/nodes/$1/proxy/metrics + + metric_relabel_configs: + - source_labels: [__name__] + regex: (kubelet_volume_stats_used_bytes) # this metric is in alpha + action: keep + + # Scrape config for service endpoints. + # + # The relabeling allows the actual service scrape endpoint to be configured + # via the following annotations: + # + # * `prometheus.io/scrape`: Only scrape services that have a value of `true` + # * `prometheus.io/scheme`: If the metrics endpoint is secured then you will need + # to set this to `https` & most likely set the `tls_config` of the scrape config. + # * `prometheus.io/path`: If the metrics path is not `/metrics` override this. + # * `prometheus.io/port`: If the metrics are exposed on a different port to the + # service then set this appropriately. + - job_name: 'kubernetes-service-endpoints' + + kubernetes_sd_configs: + - role: endpoints + + relabel_configs: + - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape] + action: keep + regex: true + - source_labels: [__meta_kubernetes_endpoints_name] + action: keep + regex: (.*node-exporter|kubecost-network-costs) + - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme] + action: replace + target_label: __scheme__ + regex: (https?) + - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path] + action: replace + target_label: __metrics_path__ + regex: (.+) + - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port] + action: replace + target_label: __address__ + regex: ([^:]+)(?::\d+)?;(\d+) + replacement: $1:$2 + - action: labelmap + regex: __meta_kubernetes_service_label_(.+) + - source_labels: [__meta_kubernetes_namespace] + action: replace + target_label: kubernetes_namespace + - source_labels: [__meta_kubernetes_service_name] + action: replace + target_label: kubernetes_name + - source_labels: [__meta_kubernetes_pod_node_name] + action: replace + target_label: kubernetes_node + metric_relabel_configs: + - source_labels: [__name__] + regex: (container_cpu_allocation|container_cpu_usage_seconds_total|container_fs_limit_bytes|container_fs_writes_bytes_total|container_gpu_allocation|container_memory_allocation_bytes|container_memory_usage_bytes|container_memory_working_set_bytes|container_network_receive_bytes_total|container_network_transmit_bytes_total|DCGM_FI_DEV_GPU_UTIL|deployment_match_labels|kube_daemonset_status_desired_number_scheduled|kube_daemonset_status_number_ready|kube_deployment_spec_replicas|kube_deployment_status_replicas|kube_deployment_status_replicas_available|kube_job_status_failed|kube_namespace_annotations|kube_namespace_labels|kube_node_info|kube_node_labels|kube_node_status_allocatable|kube_node_status_allocatable_cpu_cores|kube_node_status_allocatable_memory_bytes|kube_node_status_capacity|kube_node_status_capacity_cpu_cores|kube_node_status_capacity_memory_bytes|kube_node_status_condition|kube_persistentvolume_capacity_bytes|kube_persistentvolume_status_phase|kube_persistentvolumeclaim_info|kube_persistentvolumeclaim_resource_requests_storage_bytes|kube_pod_container_info|kube_pod_container_resource_limits|kube_pod_container_resource_limits_cpu_cores|kube_pod_container_resource_limits_memory_bytes|kube_pod_container_resource_requests|kube_pod_container_resource_requests_cpu_cores|kube_pod_container_resource_requests_memory_bytes|kube_pod_container_status_restarts_total|kube_pod_container_status_running|kube_pod_container_status_terminated_reason|kube_pod_labels|kube_pod_owner|kube_pod_status_phase|kube_replicaset_owner|kube_statefulset_replicas|kube_statefulset_status_replicas|kubecost_cluster_info|kubecost_cluster_management_cost|kubecost_cluster_memory_working_set_bytes|kubecost_load_balancer_cost|kubecost_network_internet_egress_cost|kubecost_network_region_egress_cost|kubecost_network_zone_egress_cost|kubecost_node_is_spot|kubecost_pod_network_egress_bytes_total|node_cpu_hourly_cost|node_cpu_seconds_total|node_disk_reads_completed|node_disk_reads_completed_total|node_disk_writes_completed|node_disk_writes_completed_total|node_filesystem_device_error|node_gpu_count|node_gpu_hourly_cost|node_memory_Buffers_bytes|node_memory_Cached_bytes|node_memory_MemAvailable_bytes|node_memory_MemFree_bytes|node_memory_MemTotal_bytes|node_network_transmit_bytes_total|node_ram_hourly_cost|node_total_hourly_cost|pod_pvc_allocation|pv_hourly_cost|service_selector_labels|statefulSet_match_labels|kubecost_pv_info|up) + action: keep + + + # prometheus.yml: # Sample block -- enable if using an in cluster durable store. + # remote_write: + # - url: "http://pgprometheus-adapter:9201/write" + # write_relabel_configs: + # - source_labels: [__name__] + # regex: 'container_.*_allocation|container_.*_allocation_bytes|.*_hourly_cost|kube_pod_container_resource_requests{resource="memory", unit="byte"}|container_memory_working_set_bytes|kube_pod_container_resource_requests{resource="cpu", unit="core"}|kube_pod_container_resource_requests|pod_pvc_allocation|kube_namespace_labels|kube_pod_labels' + # action: keep + # queue_config: + # max_samples_per_send: 1000 + # remote_read: + # - url: "http://pgprometheus-adapter:9201/read" + rules: + groups: + - name: CPU + rules: + - expr: sum(rate(container_cpu_usage_seconds_total{container!=""}[5m])) + record: cluster:cpu_usage:rate5m + - expr: rate(container_cpu_usage_seconds_total{container!=""}[5m]) + record: cluster:cpu_usage_nosum:rate5m + - expr: avg(irate(container_cpu_usage_seconds_total{container!="POD", container!=""}[5m])) by (container,pod,namespace) + record: kubecost_container_cpu_usage_irate + - expr: sum(container_memory_working_set_bytes{container!="POD",container!=""}) by (container,pod,namespace) + record: kubecost_container_memory_working_set_bytes + - expr: sum(container_memory_working_set_bytes{container!="POD",container!=""}) + record: kubecost_cluster_memory_working_set_bytes + - name: Savings + rules: + - expr: sum(avg(kube_pod_owner{owner_kind!="DaemonSet"}) by (pod) * sum(container_cpu_allocation) by (pod)) + record: kubecost_savings_cpu_allocation + labels: + daemonset: "false" + - expr: sum(avg(kube_pod_owner{owner_kind="DaemonSet"}) by (pod) * sum(container_cpu_allocation) by (pod)) / sum(kube_node_info) + record: kubecost_savings_cpu_allocation + labels: + daemonset: "true" + - expr: sum(avg(kube_pod_owner{owner_kind!="DaemonSet"}) by (pod) * sum(container_memory_allocation_bytes) by (pod)) + record: kubecost_savings_memory_allocation_bytes + labels: + daemonset: "false" + - expr: sum(avg(kube_pod_owner{owner_kind="DaemonSet"}) by (pod) * sum(container_memory_allocation_bytes) by (pod)) / sum(kube_node_info) + record: kubecost_savings_memory_allocation_bytes + labels: + daemonset: "true" + + # Adds option to add alert_relabel_configs to avoid duplicate alerts in alertmanager + # useful in H/A prometheus with different external labels but the same alerts + alertRelabelConfigs: + # alert_relabel_configs: + # - source_labels: [dc] + # regex: (.+)\d+ + # target_label: dc + + networkPolicy: + ## Enable creation of NetworkPolicy resources. + ## + enabled: false + + +## Optional daemonset to more accurately attribute network costs to the correct workload +## https://docs.kubecost.com/install-and-configure/advanced-configuration/network-costs-configuration +networkCosts: + enabled: false + image: + repository: gcr.io/kubecost1/kubecost-network-costs + tag: v0.17.3 + imagePullPolicy: IfNotPresent + updateStrategy: + type: RollingUpdate + # For existing Prometheus Installs, use the serviceMonitor: or prometheusScrape below. + # the below setting annotates the networkCost service endpoints for each of the network-costs pods. + # The Service is annotated with prometheus.io/scrape: "true" to automatically get picked up by the prometheus config. + # NOTE: Setting this option to true and leaving the above extraScrapeConfig "job_name: kubecost-networking" configured will cause the + # NOTE: pods to be scraped twice. + prometheusScrape: false + # Traffic Logging will enable logging the top 5 destinations for each source + # every 30 minutes. + trafficLogging: true + + logLevel: info + + # Port will set both the containerPort and hostPort to this value. + # These must be identical due to network-costs being run on hostNetwork + port: 3001 + # this daemonset can use significant resources on large clusters: https://guide.kubecost.com/hc/en-us/articles/4407595973527-Network-Traffic-Cost-Allocation + resources: + limits: # remove the limits by setting cpu: null + cpu: 500m # can be less, will depend on cluster size + # memory: it is not recommended to set a memory limit + requests: + cpu: 50m + memory: 20Mi + extraArgs: [] + config: + # Configuration for traffic destinations, including specific classification + # for IPs and CIDR blocks. This configuration will act as an override to the + # automatic classification provided by network-costs. + destinations: + # In Zone contains a list of address/range that will be + # classified as in zone. + in-zone: + # Loopback Addresses in "IANA IPv4 Special-Purpose Address Registry" + - "127.0.0.0/8" + # IPv4 Link Local Address Space + - "169.254.0.0/16" + # Private Address Ranges in RFC-1918 + - "10.0.0.0/8" # Remove this entry if using Multi-AZ Kubernetes + - "172.16.0.0/12" + - "192.168.0.0/16" + + # In Region contains a list of address/range that will be + # classified as in region. This is synonymous with cross + # zone traffic, where the regions between source and destinations + # are the same, but the zone is different. + in-region: [] + + # Cross Region contains a list of address/range that will be + # classified as non-internet egress from one region to another. + cross-region: [] + + # Internet contains a list of address/range that will be + # classified as internet traffic. This is synonymous with traffic + # that cannot be classified within the cluster. + # NOTE: Internet classification filters are executed _after_ + # NOTE: direct-classification, but before in-zone, in-region, + # NOTE: and cross-region. + internet: [] + + # Direct Classification specifically maps an ip address or range + # to a region (required) and/or zone (optional). This classification + # takes priority over in-zone, in-region, and cross-region configurations. + direct-classification: [] + # - region: "us-east1" + # zone: "us-east1-c" + # ips: + # - "10.0.0.0/24" + services: + # google-cloud-services: when set to true, enables labeling traffic metrics with google cloud + # service endpoints + google-cloud-services: true + # amazon-web-services: when set to true, enables labeling traffic metrics with amazon web service + # endpoints. + amazon-web-services: true + # azure-cloud-services: when set to true, enables labeling traffic metrics with azure cloud service + # endpoints + azure-cloud-services: true + # user defined services provide a way to define custom service endpoints which will label traffic metrics + # falling within the defined address range. + # services: + # - service: "test-service-1" + # ips: + # - "19.1.1.2" + # - service: "test-service-2" + # ips: + # - "15.128.15.2" + # - "20.0.0.0/8" + + ## Node tolerations for server scheduling to nodes with taints + ## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + ## + tolerations: [] + # - key: "key" + # operator: "Equal|Exists" + # value: "value" + # effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" + + affinity: {} + + service: + annotations: {} + labels: {} + + ## PriorityClassName + ## Ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass + priorityClassName: "" + ## PodMonitor + ## Allows scraping of network metrics from a dedicated prometheus operator setup + podMonitor: + enabled: false + additionalLabels: {} + # match the default extraScrapeConfig + additionalLabels: {} + nodeSelector: {} + annotations: {} + healthCheckProbes: {} + # readinessProbe: + # tcpSocket: + # port: 3001 + # initialDelaySeconds: 5 + # periodSeconds: 10 + # failureThreshold: 5 + # livenessProbe: + # tcpSocket: + # port: 3001 + # initialDelaySeconds: 5 + # periodSeconds: 10 + # failureThreshold: 5 + additionalSecurityContext: {} + # readOnlyRootFilesystem: true + +## Kubecost Deployment Configuration +## Used for HA mode in Business & Enterprise tier +## +kubecostDeployment: + replicas: 1 + # deploymentStrategy: + # rollingUpdate: + # maxSurge: 1 + # maxUnavailable: 1 + # type: RollingUpdate + labels: {} + annotations: {} + + +## Kubecost Forecasting forecasts future cost patterns based on historical +## patterns observed by Kubecost. +forecasting: + enabled: true + + # fullImageName overrides the default image construction logic. The exact + # image provided (registry, image, tag) will be used for the forecasting + # container. + # Example: fullImageName: gcr.io/kubecost1/forecasting:v0.0.1 + fullImageName: gcr.io/kubecost1/kubecost-modeling:v0.1.12 + imagePullPolicy: IfNotPresent + + # Resource specification block for the forecasting container. + resources: + requests: + cpu: 200m + memory: 300Mi + limits: + cpu: 1500m + memory: 1Gi + + # Set environment variables for the forecasting container as key/value pairs. + env: + # -t is the worker timeout which primarily affects model training time; + # if it is not high enough, training workers may die mid training + "GUNICORN_CMD_ARGS": "--log-level info -t 1200" + + # Define a priority class for the forecasting Deployment. + priority: + enabled: false + name: "" + + # Define a nodeSelector for the forecasting Deployment. + nodeSelector: {} + + # Define tolerations for the forecasting Deployment. + tolerations: [] + + # Define Pod affinity for the forecasting Deployment. + affinity: {} + + # Define a readiness probe for the forecasting container + readinessProbe: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 10 + failureThreshold: 200 + + # Define a liveness probe for the forecasting container. + livenessProbe: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 10 + failureThreshold: 200 + +## The Kubecost Aggregator is a high scale implementation of Kubecost intended +## for large datasets and/or high query load. At present, this should only be +## enabled when recommended by Kubecost staff. +## +kubecostAggregator: + # deployMethod determines how Aggregator is deployed. Current options are + # "singlepod" (within cost-analyzer Pod) "statefulset" (separate + # StatefulSet), and "disabled". Only use "disabled" if this is a secondary + # Federated ETL cluster which does not need to answer queries. + deployMethod: singlepod + + # fullImageName overrides the default image construction logic. The exact + # image provided (registry, image, tag) will be used for aggregator. + # fullImageName: + imagePullPolicy: IfNotPresent + + # For legacy configuration support, `enabled: true` overrides deployMethod + # and causes `deployMethod: "statefulset"` + enabled: false + + # Replicas sets the number of Aggregator replicas. It only has an effect if + # `deployMethod: "statefulset"` + replicas: 1 + + # stagingEmptyDirSizeLimit changes how large the "staging" + # /var/configs/waterfowl emptyDir is. It only takes effect in StatefulSet + # configurations of Aggregator, other configurations are unaffected. + # + # It should be set to approximately 8x the size of the largest bingen file in + # object storage. For example, if your largest bingen file is a daily + # Allocation file with size 300MiB, this value should be set to approximately + # 2400Mi. In most environments, the default should suffice. + stagingEmptyDirSizeLimit: 2Gi + + # this is the number of partitions the datastore is split into for copying + # the higher this number, the lower the ram usage but the longer it takes for + # new data to show in the kubecost UI + # set to 0 for max partitioning (minimum possible ram usage, but the slowest) + # the default of 25 is sufficient for 95%+ of users. This should only be modified + # after consulting with Kubecost's support team + numDBCopyPartitions: 1 + logLevel: info + + # env: has been removed to avoid unknown issues that would be caused by + # customizations that were required to run aggregator in previous versions + # extraEnv: can be used to add new environment variables to the aggregator pod + + # the below settings should only be modified with support from Kubecost staff + + # How many threads the read database is configured with (i.e. Kubecost API / + # UI queries). If increasing this value, it is recommended to increase the + # aggregator's memory requests & limits. + # default: 1 + dbReadThreads: 1 + # How many threads the write database is configured with (i.e. ingestion of + # new data from S3). If increasing this value, it is recommended to increase + # the aggregator's memory requests & limits. + # default: 1 + dbWriteThreads: 1 + # How many threads to use when ingesting Asset/Allocation/CloudCost data + # from the federated store bucket. In most cases the default is sufficient, + # but can be increased if trying to backfill historical data. + # default: 1 + dbConcurrentIngestionCount: 1 + # dbCopyFull: "true" can improve the time it takes to copy the write DB, + # at the expense of additional memory usages. + dbCopyFull: false + # Memory limit applied to read database connections. + # default: 0GB is no limit + dbMemoryLimit: 0GB + # Memory limit applied to write database connections. + # default: 0GB is no limit + dbWriteMemoryLimit: 0GB + # How much data to ingest from the federated store bucket, and how much data + # to keep in the DB before rolling the data off. + # + # Note: If increasing this value to backfill historical data, it will take + # time to gradually ingest and process those historical ETL files. Consider + # also increasing the resources available to the aggregator as well as the + # refresh and concurrency env vars. + # + # default: 91 + etlDailyStoreDurationDays: 91 + # Trim memory on close, only change if advised by Kubecost support. + dbTrimMemoryOnClose: true + + persistentConfigsStorage: + storageClass: "" # default storage class + storageRequest: 1Gi + aggregatorDbStorage: + storageClass: "" # default storage class + storageRequest: 128Gi + + resources: {} + # requests: + # cpu: 1000m + # memory: 1Gi + + readinessProbe: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 10 + failureThreshold: 200 + + ## Set additional environment variables for the aggregator pod + # extraEnv: + # - name: SOME_VARIABLE + # value: "some_value" + + ## Add a priority class to the aggregator pod + # priority: + # enabled: false + # name: "" + + ## Optional - add extra ports to the aggregator container. For kubecost development purposes only - not recommended for users. + # extraPorts: [] + # - name: debug + # port: 40000 + # targetPort: 40000 + # containerPort: 40000 + + ## Define a securityContext for the aggregator pod. This will take highest precedence. + # securityContext: {} + + ## Define the container-level security context for the aggregator pod. This will take highest precedence. + # containerSecurityContext: {} + + ## Provide a Service Account name for aggregator. + # serviceAccountName: "" + + ## Define a nodeSelector for the aggregator pod + # nodeSelector: {} + + ## Define tolerations for the aggregator pod + # tolerations: [] + + ## Define Pod affinity for the aggregator pod + # affinity: {} + + ## Define extra volumes for the aggregator pod + # extraVolumes: [] + + ## Define extra volumemounts for the aggregator pod + # extraVolumeMounts: [] + + ## Creates a new container/pod to retrieve CloudCost data. By default it uses + ## the same serviceaccount as the cost-analyzer pod. A custom serviceaccount + ## can be specified. + cloudCost: + # The cloudCost component of Aggregator depends on + # kubecostAggregator.deployMethod: + # kA.dM = "singlepod" -> cloudCost is run as container inside cost-analyzer + # kA.dM = "statefulset" -> cloudCost is run as single-replica Deployment + resources: {} + # requests: + # cpu: 1000m + # memory: 1Gi + # refreshRateHours: + # queryWindowDays: + # runWindowDays: + # serviceAccountName: + readinessProbe: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 10 + failureThreshold: 200 + + ## Add a nodeSelector for aggregator cloud costs + # nodeSelector: {} + + ## Tolerations for the aggregator cloud costs + # tolerations: [] + + ## Affinity for the aggregator cloud costs + # affinity: {} + + ## ServiceAccount for the aggregator cloud costs + # serviceAccountName: "" + + ## Define environment variables for cloud cost + # env: {} + + ## Define extra volumes for the cloud cost pod + # extraVolumes: [] + + ## Define extra volumemounts for the cloud cost pod + # extraVolumeMounts: [] + + ## Configure the Collections service for aggregator. + # collections: + # cache: + # enabled: false + + # Jaeger is an optional container attached to wherever the Aggregator + # container is running. It is used for performance investigation. Enable if + # Kubecost Support asks. + jaeger: + enabled: false + image: jaegertracing/all-in-one + imageVersion: latest + # containerSecurityContext: + +## Kubecost Multi-cluster Diagnostics (beta) +## A single view into the health of all agent clusters. Each agent cluster sends +## its diagnostic data to a storage bucket. Future versions may include +## repairing & alerting from the primary. +## Ref: https://docs.kubecost.com/install-and-configure/install/multi-cluster-diagnostics +## +diagnostics: + enabled: true + + ## The primary aggregates all diagnostic data and handles API requests. It's + ## also responsible for deleting diagnostic data (on disk & bucket) beyond + ## retention. When in readonly mode it does not push its own diagnostic data + ## to the bucket. + primary: + enabled: false + retention: "7d" + readonly: false + + ## How frequently to run & push diagnostics. Defaults to 5 minutes. + pollingInterval: "300s" + + ## Creates a new Diagnostic file in the bucket for every run. + keepDiagnosticHistory: false + + ## Pushes the cluster's Kubecost Helm Values to the bucket once upon startup. + ## This may contain sensitive information and is roughly 30kb per cluster. + collectHelmValues: false + + ## By default, the Multi-cluster Diagnostics service runs within the + ## cost-model container in the cost-analyzer pod. For higher availability, it + ## can be run as a separate deployment. + deployment: + enabled: false + resources: + requests: + cpu: "10m" + memory: "20Mi" + env: {} + labels: {} + securityContext: {} + containerSecurityContext: {} + nodeSelector: {} + tolerations: [] + affinity: {} + +## Provide a full name override for the diagnostics Deployment. +# diagnosticsFullnameOverride: "" + +# Kubecost Cluster Controller for Right Sizing and Cluster Turndown +clusterController: + enabled: false + image: + repository: gcr.io/kubecost1/cluster-controller + tag: v0.16.5 + imagePullPolicy: IfNotPresent + ## PriorityClassName + ## Ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass + priorityClassName: "" + # Set custom tolerations for the cluster controller. + tolerations: [] + actionConfigs: + # this configures the Kubecost Cluster Turndown action + # for more details, see documentation at https://github.com/kubecost/cluster-turndown/tree/develop?tab=readme-ov-file#setting-a-turndown-schedule + clusterTurndown: [] + # - name: my-schedule + # start: "2024-02-09T00:00:00Z" + # end: "2024-02-09T12:00:00Z" + # repeat: daily + # - name: my-schedule2 + # start: "2024-02-09T00:00:00Z" + # end: "2024-02-09T01:00:00Z" + # repeat: weekly + # this configures the Kubecost Namespace Turndown action + # for more details, see documentation at https://docs.kubecost.com/using-kubecost/navigating-the-kubecost-ui/savings/savings-actions#namespace-turndown + namespaceTurndown: + # - name: my-ns-turndown-action + # dryRun: false + # schedule: "0 0 * * *" + # type: Scheduled + # targetObjs: + # - namespace + # keepPatterns: + # - ignorednamespace + # keepLabels: + # turndown: ignore + # params: + # minNamespaceAge: 4h + # this configures the Kubecost Cluster Sizing action + # for more details, see documentation at https://docs.kubecost.com/using-kubecost/navigating-the-kubecost-ui/savings/savings-actions#cluster-sizing + clusterRightsize: + # startTime: '2024-01-02T15:04:05Z' + # frequencyMinutes: 1440 + # lastCompleted: '' + # recommendationParams: + # window: 48h + # architecture: '' + # targetUtilization: 0.8 + # minNodeCount: 1 + # allowSharedCore: false + # allowCostIncrease: false + # recommendationType: '' + # This configures the Kubecost Continuous Request Sizing Action + # + # Using this configuration overrides annotation-based configuration of + # Continuous Request Sizing. Annotation configuration will be ignored while + # this configuration method is present in the cluster. + # + # For more details, see documentation at https://docs.kubecost.com/using-kubecost/navigating-the-kubecost-ui/savings/savings-actions#automated-request-sizing + containerRightsize: + # Workloads can be selected by an _exact_ key (namespace, controllerKind, + # controllerName). This will only match a single controller. The cluster + # ID is current irrelevant because Cluster Controller can only modify + # workloads within the cluster it is running in. + # workloads: + # - clusterID: cluster-one + # namespace: my-namespace + # controllerKind: deployment + # controllerName: my-controller + # An alternative to exact key selection is filter selection. The filters + # are syntactically identical to Kubecost's "v2" filters [1] but only + # support a small set of filter fields, those being: + # - namespace + # - controllerKind + # - controllerName + # - label + # - annotation + # + # If multiple filters are listed, they will be ORed together at the top + # level. + # + # See the examples below. + # + # [1] https://docs.kubecost.com/apis/filters-api + # filterConfig: + # - filter: | + # namespace:"abc"+controllerKind:"deployment" + # - filter: | + # controllerName:"abc123"+controllerKind:"daemonset" + # - filter: | + # namespace:"foo"+controllerKind!:"statefulset" + # - filter: | + # namespace:"bar","baz" + # schedule: + # start: "2024-01-30T15:04:05Z" + # frequencyMinutes: 5 + # recommendationQueryWindow: "48h" + # lastModified: '' + # targetUtilizationCPU: 0.8 # results in a cpu request setting that is 20% higher than the max seen over last 48h + # targetUtilizationMemory: 0.8 # results in a RAM request setting that is 20% higher than the max seen over last 48h + + kubescaler: + # If true, will cause all (supported) workloads to be have their requests + # automatically right-sized on a regular basis. + defaultResizeAll: false +# fqdn: kubecost-cluster-controller.kubecost.svc.cluster.local:9731 + namespaceTurndown: + rbac: + enabled: true + +reporting: + # Kubecost bug report feature: Logs access/collection limited to .Release.Namespace + # Ref: http://docs.kubecost.com/bug-report + logCollection: true + # Basic frontend analytics + productAnalytics: true + + # Report Javascript errors + errorReporting: true + valuesReporting: true + # googleAnalyticsTag allows you to embed your Google Global Site Tag to track usage of Kubecost. + # googleAnalyticsTag is only included in our Enterprise offering. + # googleAnalyticsTag: G-XXXXXXXXX + +serviceMonitor: # the kubecost included prometheus uses scrapeConfigs and does not support service monitors. The following options assume an existing prometheus that supports serviceMonitors. + enabled: false + interval: 1m + scrapeTimeout: 10s + additionalLabels: {} + metricRelabelings: [] + relabelings: [] + networkCosts: + enabled: false + interval: 1m + scrapeTimeout: 10s + additionalLabels: {} + metricRelabelings: [] + relabelings: [] + aggregatorMetrics: + enabled: false + interval: 1m + scrapeTimeout: 10s + additionalLabels: {} + metricRelabelings: [] + relabelings: + - action: replace + sourceLabels: + - __meta_kubernetes_namespace + targetLabel: namespace +prometheusRule: + enabled: false + additionalLabels: {} + +supportNFS: false +# initChownDataImage ensures all Kubecost filepath permissions on PV or local storage are set up correctly. +initChownDataImage: "busybox" # Supports a fully qualified Docker image, e.g. registry.hub.docker.com/library/busybox:latest +initChownData: + resources: {} + # requests: + # cpu: "50m" + # memory: "20Mi" + +grafana: + # namespace_datasources: kubecost # override the default namespace here + # namespace_dashboards: kubecost # override the default namespace here + rbac: + create: true + + serviceAccount: + create: true + name: "" + + ## Provide a full name override for the Grafana Deployment. + # fullnameOverride: "" + ## Provide a name override for the Grafana Deployment. + # nameOverride: "" + + ## Configure grafana datasources + ## ref: http://docs.grafana.org/administration/provisioning/#datasources + ## + # datasources: + # datasources.yaml: + # apiVersion: 1 + # datasources: + # - name: prometheus-kubecost + # type: prometheus + # url: http://kubecost-prometheus-server.kubecost.svc.cluster.local + # access: proxy + # isDefault: false + # jsonData: + # httpMethod: POST + # prometheusType: Prometheus + # prometheusVersion: 2.35.0 + # timeInterval: 1m + + ## Number of replicas for the Grafana deployment + replicas: 1 + + ## Deployment strategy for the Grafana deployment + deploymentStrategy: RollingUpdate + + ## Readiness probe for the Grafana deployment + readinessProbe: + httpGet: + path: /api/health + port: 3000 + + ## Liveness probe for the Grafana deployment + livenessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 60 + timeoutSeconds: 30 + failureThreshold: 10 + + ## Container image settings for the Grafana deployment + image: + repository: grafana/grafana + tag: 10.4.3 + pullPolicy: IfNotPresent + + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + # pullSecrets: + # - myRegistrKeySecretName + + ## Pod-level security context for the Grafana deployment. Recommended let global defaults take effect. + securityContext: {} + # runAsUser: 472 + # fsGroup: 472 + + ## PriorityClassName for the Grafana deployment + priorityClassName: "" + + ## Container image settings for Grafana initContainer used to download dashboards. Will only be used when dashboards are present. + downloadDashboardsImage: + repository: curlimages/curl + tag: latest + pullPolicy: IfNotPresent + + ## Pod Annotations for the Grafana deployment + podAnnotations: {} + + ## Deployment annotations for the Grafana deployment + annotations: {} + + ## Expose the Grafana service to be accessed from outside the cluster (LoadBalancer service). + ## or access it from within the cluster (ClusterIP service). Set the service type and the port to serve it. + service: + type: ClusterIP + port: 80 + annotations: {} + labels: {} + + ## Ingress service for the Grafana deployment + ingress: + enabled: false + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + labels: {} + path: / + pathType: Prefix + hosts: + - chart-example.local + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + + ## Resource requests and limits for the Grafana deployment + resources: {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + + ## Node labels for pod assignment of the Grafana deployment + nodeSelector: {} + + ## Tolerations for pod assignment of the Grafana deployment + tolerations: [] + + ## Affinity for pod assignment of the Grafana deployment + affinity: {} + + ## Enable persistence using Persistent Volume Claims of the Grafana deployment + persistence: + enabled: false + # storageClassName: default + # accessModes: + # - ReadWriteOnce + # size: 10Gi + # annotations: {} + # subPath: "" + # existingClaim: + + ## Admin user for Grafana + adminUser: admin + + ## Admin password for Grafana + adminPassword: strongpassword + + ## Use an alternate scheduler for the Grafana deployment + # schedulerName: + + ## Extra environment variables that will be passed onto Grafana deployment pods + env: {} + + ## The name of a secret for Grafana in the same Kubernetes namespace which contain values to be added to the environment + ## This can be useful for auth tokens, etc + envFromSecret: "" + + ## Additional Grafana server secret mounts + ## Defines additional mounts with secrets. Secrets must be manually created in the namespace. + extraSecretMounts: [] + # - name: secret-files + # mountPath: /etc/secrets + # secretName: grafana-secret-files + # readOnly: true + + ## List of Grafana plugins + plugins: [] + # - digrich-bubblechart-panel + # - grafana-clock-panel + + ## Grafana dashboard providers + ## ref: http://docs.grafana.org/administration/provisioning/#dashboards + ## + ## `path` must be /var/lib/grafana/dashboards/ + ## + dashboardProviders: {} + # dashboardproviders.yaml: + # apiVersion: 1 + # providers: + # - name: 'default' + # orgId: 1 + # folder: '' + # type: file + # disableDeletion: false + # editable: true + # options: + # path: /var/lib/grafana/dashboards/default + + ## Configure Grafana dashboard to import + ## NOTE: To use dashboards you must also enable/configure dashboardProviders + ## ref: https://grafana.com/dashboards + ## + ## dashboards per provider, use provider name as key. + ## + dashboards: {} + # default: + # prometheus-stats: + # gnetId: 3662 + # revision: 2 + # datasource: Prometheus + + ## Reference to external Grafana ConfigMap per provider. Use provider name as key and ConfiMap name as value. + ## A provider dashboards must be defined either by external ConfigMaps or in values.yaml, not in both. + ## ConfigMap data example: + ## + ## data: + ## example-dashboard.json: | + ## RAW_JSON + ## + dashboardsConfigMaps: {} + # default: "" + + ## LDAP Authentication for Grafana can be enabled with the following values on grafana.ini + ## NOTE: Grafana will fail to start if the value for ldap.toml is invalid + # auth.ldap: + # enabled: true + # allow_sign_up: true + # config_file: /etc/grafana/ldap.toml + + ## Grafana's LDAP configuration + ## Templated by the template in _helpers.tpl + ## NOTE: To enable the grafana.ini must be configured with auth.ldap.enabled + ## ref: http://docs.grafana.org/installation/configuration/#auth-ldap + ## ref: http://docs.grafana.org/installation/ldap/#configuration + ldap: + # `existingSecret` is a reference to an existing secret containing the ldap configuration + # for Grafana in a key `ldap-toml`. + existingSecret: "" + # `config` is the content of `ldap.toml` that will be stored in the created secret + config: "" + # config: |- + # verbose_logging = true + + # [[servers]] + # host = "my-ldap-server" + # port = 636 + # use_ssl = true + # start_tls = false + # ssl_skip_verify = false + # bind_dn = "uid=%s,ou=users,dc=myorg,dc=com" + + ## Grafana's SMTP configuration + ## NOTE: To enable, grafana.ini must be configured with smtp.enabled + ## ref: http://docs.grafana.org/installation/configuration/#smtp + smtp: + # `existingSecret` is a reference to an existing secret containing the smtp configuration + # for Grafana in keys `user` and `password`. + existingSecret: "" + + ## Grafana sidecars that collect the configmaps with specified label and stores the included files them into the respective folders + ## Requires at least Grafana 5 to work and can't be used together with parameters dashboardProviders, datasources and dashboards + sidecar: + image: + repository: kiwigrid/k8s-sidecar + tag: 1.27.2 + pullPolicy: IfNotPresent + resources: {} + dashboards: + enabled: true + # label that the configmaps with dashboards are marked with + label: grafana_dashboard + labelValue: "1" + # set sidecar ERROR_THROTTLE_SLEEP env var from default 5s to 0s -> fixes https://github.com/kubecost/cost-analyzer-helm-chart/issues/877 + annotations: {} + error_throttle_sleep: 0 + folder: /tmp/dashboards + datasources: + # dataSourceFilename: foo.yml # If you need to change the name of the datasource file + enabled: false + error_throttle_sleep: 0 + # label that the configmaps with datasources are marked with + label: grafana_datasource + + ## Grafana's primary configuration + ## NOTE: values in map will be converted to ini format + ## ref: http://docs.grafana.org/installation/configuration/ + ## + ## For grafana to be accessible, add the path to root_url. For example, if you run kubecost at www.foo.com:9090/kubecost + ## set root_url to "%(protocol)s://%(domain)s:%(http_port)s/kubecost/grafana". No change is necessary here if kubecost runs at a root URL + grafana.ini: + server: + serve_from_sub_path: false # Set to false on Grafana v10+ + root_url: "%(protocol)s://%(domain)s:%(http_port)s/grafana" + paths: + data: /var/lib/grafana/data + logs: /var/log/grafana + plugins: /var/lib/grafana/plugins + provisioning: /etc/grafana/provisioning + analytics: + check_for_updates: true + log: + mode: console + grafana_net: + url: https://grafana.net + auth.anonymous: + enabled: true + org_role: Editor + org_name: Main Org. + +serviceAccount: + create: true # Set this to false if you're bringing your own service account. + annotations: {} + # name: kc-test + +awsstore: + useAwsStore: false + imageNameAndVersion: gcr.io/kubecost1/awsstore:latest # Name and version of the container image for AWSStore. + createServiceAccount: false + ## PriorityClassName + ## Ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass + priorityClassName: "" + # Use a custom nodeSelector for AWSStore. + nodeSelector: {} + # kubernetes.io/arch: amd64 + ## Annotations for the AWSStore ServiceAccount. + annotations: {} + +## Federated ETL Architecture +## Ref: https://docs.kubecost.com/install-and-configure/install/multi-cluster/federated-etl +## +federatedETL: + + ## If true, installs the minimal set of components required for a Federated ETL cluster. + agentOnly: false + + ## If true, push ETL data to the federated storage bucket + federatedCluster: false + + ## If true, this cluster will be able to read from the federated-store but will + ## not write to it. This is useful in situations when you want to deploy a + ## primary cluster, but don't want the primary cluster's ETL data to be + ## pushed to the bucket + readOnlyPrimary: false + + ## If true, changes the dir of S3 backup to the Federated combined store. + ## Commonly used when transitioning from Thanos to Federated ETL architecture. + redirectS3Backup: false + + ## If true, will query metrics from a central PromQL DB (e.g. Amazon Managed + ## Prometheus) + useMultiClusterDB: false + +## Kubecost Admission Controller (beta feature) +## To use this feature, ensure you have run the `create-admission-controller.sh` +## script. This generates a k8s secret with TLS keys/certificats and a +## corresponding CA bundle. +## +kubecostAdmissionController: + enabled: false + secretName: webhook-server-tls + caBundle: ${CA_BUNDLE} + +# Enables or disables the Cost Event Audit pipeline, which tracks recent changes at cluster level +# and provides an estimated cost impact via the Kubecost Predict API. +# +# It is disabled by default to avoid problems in high-scale environments. +costEventsAudit: + enabled: false + +## Disable updates to kubecost from the frontend UI and via POST request +## This feature is considered beta, entrprise users should use teams: +## https://docs.kubecost.com/using-kubecost/navigating-the-kubecost-ui/teams +# readonly: false + +# # These configs can also be set from the Settings page in the Kubecost product +# # UI. Values in this block override config changes in the Settings UI on pod +# # restart +# kubecostProductConfigs: +# # An optional list of cluster definitions that can be added for frontend +# # access. The local cluster is *always* included by default, so this list is +# # for non-local clusters. +# clusters: +# - name: "Cluster A" +# address: http://cluster-a.kubecost.com:9090 +# # Optional authentication credentials - only basic auth is currently supported. +# auth: +# type: basic +# # Secret name should be a secret formatted based on: https://github.com/kubecost/docs/blob/main/ingress-examples.md +# secretName: cluster-a-auth +# # Or pass auth directly as base64 encoded user:pass +# data: YWRtaW46YWRtaW4= +# # Or user and pass directly +# user: admin +# pass: admin +# - name: "Cluster B" +# address: http://cluster-b.kubecost.com:9090 +# # Enabling customPricesEnabled and defaultModelPricing instructs Kubecost to +# # use these custom monthly resource prices when reporting node costs. Note, +# # that the below configuration is for the monthly cost of the resource. +# # Kubecost considers there to be 730 hours in a month. Also note, that these +# # configurations will have no effect on metrics emitted such as +# # `node_ram_hourly_cost` or `node_cpu_hourly_cost`. +# # Ref: https://docs.kubecost.com/install-and-configure/install/provider-installations/air-gapped +# customPricesEnabled: false +# defaultModelPricing: +# enabled: true +# CPU: "28.0" +# spotCPU: "4.86" +# RAM: "3.09" +# spotRAM: "0.65" +# GPU: "693.50" +# spotGPU: "225.0" +# storage: "0.04" +# zoneNetworkEgress: "0.01" +# regionNetworkEgress: "0.01" +# internetNetworkEgress: "0.12" +# # The cluster profile represents a predefined set of parameters to use when calculating savings. +# # Possible values are: [ development, production, high-availability ] +# clusterProfile: production +# spotLabel: lifecycle +# spotLabelValue: Ec2Spot +# gpuLabel: gpu +# gpuLabelValue: true +# alibabaServiceKeyName: "" +# alibabaServiceKeyPassword: "" +# awsServiceKeyName: ACCESSKEYID +# awsServiceKeyPassword: fakepassword # Only use if your values.yaml are stored encrypted. Otherwise provide an existing secret via serviceKeySecretName +# awsSpotDataRegion: us-east-1 +# awsSpotDataBucket: spot-data-feed-s3-bucket +# awsSpotDataPrefix: dev +# athenaProjectID: "530337586277" # The AWS AccountID where the Athena CUR is. Generally your masterpayer account +# athenaBucketName: "s3://aws-athena-query-results-530337586277-us-east-1" +# athenaRegion: us-east-1 +# athenaDatabase: athenacurcfn_athena_test1 +# athenaTable: "athena_test1" +# athenaWorkgroup: "primary" # The default workgroup in AWS is 'primary' +# masterPayerARN: "" +# projectID: "123456789" # Also known as AccountID on AWS -- the current account/project that this instance of Kubecost is deployed on. +# gcpSecretName: gcp-secret # Name of a secret representing the gcp service key +# gcpSecretKeyName: compute-viewer-kubecost-key.json # Name of the secret's key containing the gcp service key +# bigQueryBillingDataDataset: billing_data.gcp_billing_export_v1_01AC9F_74CF1D_5565A2 +# labelMappingConfigs: # names of k8s labels or annotations used to designate different allocation concepts +# enabled: true +# owner_label: "owner" +# team_label: "team" +# department_label: "dept" +# product_label: "product" +# environment_label: "env" +# namespace_external_label: "kubernetes_namespace" # external labels/tags are used to map external cloud costs to kubernetes concepts +# cluster_external_label: "kubernetes_cluster" +# controller_external_label: "kubernetes_controller" +# product_external_label: "kubernetes_label_app" +# service_external_label: "kubernetes_service" +# deployment_external_label: "kubernetes_deployment" +# owner_external_label: "kubernetes_label_owner" +# team_external_label: "kubernetes_label_team" +# environment_external_label: "kubernetes_label_env" +# department_external_label: "kubernetes_label_department" +# statefulset_external_label: "kubernetes_statefulset" +# daemonset_external_label: "kubernetes_daemonset" +# pod_external_label: "kubernetes_pod" +# grafanaURL: "" +# # Provide a mapping from Account ID to a readable Account Name in a key/value object. Provide Account IDs as they are displayed in CloudCost +# # as the 'key' and the Account Name associated with it as the 'value' +# cloudAccountMapping: +# EXAMPLE_ACCOUNT_ID: EXAMPLE_ACCOUNT_NAME +# clusterName: "" # clusterName is the default context name in settings. +# clusterAccountID: "" # Manually set Account property for assets +# currencyCode: "USD" # official support for USD, AUD, BRL, CAD, CHF, CNY, DKK, EUR, GBP, IDR, INR, JPY, NOK, PLN, SEK +# azureBillingRegion: US # Represents 2-letter region code, e.g. West Europe = NL, Canada = CA. ref: https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes +# azureSubscriptionID: 0bd50fdf-c923-4e1e-850c-196dd3dcc5d3 +# azureClientID: f2ef6f7d-71fb-47c8-b766-8d63a19db017 +# azureTenantID: 72faf3ff-7a3f-4597-b0d9-7b0b201bb23a +# azureClientPassword: fake key # Only use if your values.yaml are stored encrypted. Otherwise provide an existing secret via serviceKeySecretName +# azureOfferDurableID: "MS-AZR-0003p" +# discount: "" # percentage discount applied to compute +# negotiatedDiscount: "" # custom negotiated cloud provider discount +# defaultIdle: false +# serviceKeySecretName: "" # Use an existing AWS or Azure secret with format as in aws-service-key-secret.yaml or azure-service-key-secret.yaml. Leave blank if using createServiceKeySecret +# createServiceKeySecret: true # Creates a secret representing your cloud service key based on data in values.yaml. If you are storing unencrypted values, add a secret manually +# sharedNamespaces: "" # namespaces with shared workloads, example value: "kube-system\,ingress-nginx\,kubecost\,monitoring" +# sharedOverhead: "" # value representing a fixed external cost per month to be distributed among aggregations. +# shareTenancyCosts: true # enable or disable sharing costs such as cluster management fees (defaults to "true" on Settings page) +# metricsConfigs: # configuration for metrics emitted by Kubecost +# disabledMetrics: [] # list of metrics that Kubecost will not emit. Note that disabling metrics can lead to unexpected behavior in the cost-model. +# productKey: # Apply enterprise product license +# enabled: false +# key: "" +# secretname: productkeysecret # Reference an existing k8s secret created from a file named productkey.json of format { "key": "enterprise-key-here" }. If the secretname is specified, a configmap with the key will not be created. +# mountPath: "/some/custom/path/productkey.json" # (use instead of secretname) Declare the path at which the product key file is mounted (eg. by a secrets provisioner). The file must be of format { "key": "enterprise-key-here" }. +# # The following block enables the use of a custom SMTP server which overrides Kubecost's built-in, external SMTP server for alerts and reports +# smtp: +# config: | +# { +# "sender_email": "", +# "host": "", +# "port": 587, +# "authentication": true, +# "username": "", +# "password": "", +# "secure": true +# } +# secretname: smtpconfigsecret # Reference an existing k8s secret created from a file named smtp.json of format specified by config above. If the secretname is specified, a configmap with the key will not be created. +# mountPath: "/some/custom/path/smtp.json" # (use instead of secretname) Declare the path at which the SMTP config file is mounted (eg. by a secrets provisioner). The file must be of format specified by config above. +# carbonEstimates: false # Enables Kubecost beta carbon estimation endpoints /assets/carbon and /allocations/carbon + + ## Specify an existing Kubernetes Secret holding the cloud integration information. This Secret must contain + ## a key with name `cloud-integration.json` and the contents must be in a specific format. It is expected + ## to exist in the release Namespace. This is mutually exclusive with cloudIntegrationJSON where only one must be defined. + # cloudIntegrationSecret: "cloud-integration" + + ## Specify the cloud integration information in JSON form if pointing to an existing Secret is not desired or you'd rather + ## define the cloud integration information directly in the values file. This will result in a new Secret being created + ## named `cloud-integration` in the release Namespace. It is mutually exclusive with the cloudIntegrationSecret where only one must be defined. + # cloudIntegrationJSON: |- + # { + # "aws": [ + # { + # "athenaBucketName": "s3://AWS_cloud_integration_athenaBucketName", + # "athenaRegion": "AWS_cloud_integration_athenaRegion", + # "athenaDatabase": "AWS_cloud_integration_athenaDatabase", + # "athenaTable": "AWS_cloud_integration_athenaBucketName", + # "projectID": "AWS_cloud_integration_athena_projectID", + # "serviceKeyName": "AWS_cloud_integration_athena_serviceKeyName", + # "serviceKeySecret": "AWS_cloud_integration_athena_serviceKeySecret" + # } + # ], + # "azure": [ + # { + # "azureSubscriptionID": "my-subscription-id", + # "azureStorageAccount": "my-storage-account", + # "azureStorageAccessKey": "my-storage-access-key", + # "azureStorageContainer": "my-storage-container" + # } + # ], + # "gcp": [ + # { + # "projectID": "my-project-id", + # "billingDataDataset": "detailedbilling.my-billing-dataset", + # "key": { + # "type": "service_account", + # "project_id": "my-project-id", + # "private_key_id": "my-private-key-id", + # "private_key": "my-pem-encoded-private-key", + # "client_email": "my-service-account-name@my-project-id.iam.gserviceaccount.com", + # "client_id": "my-client-id", + # "auth_uri": "auth-uri", + # "token_uri": "token-uri", + # "auth_provider_x509_cert_url": "my-x509-provider-cert", + # "client_x509_cert_url": "my-x509-cert-url" + # } + # } + # ] + # } + + # ingestPodUID: false # Enables using UIDs to uniquely ID pods. This requires either Kubecost's replicated KSM metrics, or KSM v2.1.0+. This may impact performance, and changes the default cost-model allocation behavior. + # regionOverrides: "region1,region2,region3" # list of regions which will override default costmodel provider regions + +# Explicit names of various ConfigMaps to use. If not set, a default will apply. +# pricingConfigmapName: "" +# productConfigmapName: "" +# smtpConfigmapName: "" + +# -- Array of extra K8s manifests to deploy +## Note: Supports use of custom Helm templates +extraObjects: [] +# Cloud Billing Integration: +# - apiVersion: v1 +# kind: Secret +# metadata: +# name: cloud-integration +# namespace: kubecost +# type: Opaque +# data: +# cloud-integration.json: BASE64_SECRET +# Istio: +# - apiVersion: networking.istio.io/v1alpha3 +# kind: VirtualService +# metadata: +# name: my-virtualservice +# spec: +# hosts: +# - kubecost.myorg.com +# gateways: +# - my-gateway +# http: +# - route: +# - destination: +# host: kubecost.kubecost.svc.cluster.local +# port: +# number: 80 diff --git a/charts/new-relic/nri-bundle/5.0.88/.helmignore b/charts/new-relic/nri-bundle/5.0.88/.helmignore new file mode 100644 index 000000000..50af03172 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/.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.88/Chart.lock b/charts/new-relic/nri-bundle/5.0.88/Chart.lock new file mode 100644 index 000000000..99e6092b6 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/Chart.lock @@ -0,0 +1,39 @@ +dependencies: +- name: newrelic-infrastructure + repository: https://newrelic.github.io/nri-kubernetes + version: 3.34.3 +- name: nri-prometheus + repository: https://newrelic.github.io/nri-prometheus + version: 2.1.18 +- name: newrelic-prometheus-agent + repository: https://newrelic.github.io/newrelic-prometheus-configurator + version: 1.14.3 +- name: nri-metadata-injection + repository: https://newrelic.github.io/k8s-metadata-injection + version: 4.20.3 +- name: newrelic-k8s-metrics-adapter + repository: https://newrelic.github.io/newrelic-k8s-metrics-adapter + version: 1.11.1 +- name: kube-state-metrics + repository: https://prometheus-community.github.io/helm-charts + version: 5.12.1 +- name: nri-kube-events + repository: https://newrelic.github.io/nri-kube-events + version: 3.10.2 +- name: newrelic-logging + repository: https://newrelic.github.io/helm-charts + version: 1.22.3 +- name: newrelic-pixie + repository: https://newrelic.github.io/helm-charts + version: 2.1.4 +- name: k8s-agents-operator + repository: https://newrelic.github.io/k8s-agents-operator + version: 0.10.0 +- 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.11.1 +digest: sha256:9b45f47e65124e2da7427aeeed7f8063df7120f4a99dfaa66add2277d2d633f9 +generated: "2024-07-29T20:45:27.937530605Z" diff --git a/charts/new-relic/nri-bundle/5.0.88/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/Chart.yaml new file mode 100644 index 000000000..3c169a8da --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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: file://./charts/newrelic-infrastructure + version: 3.34.3 +- condition: prometheus.enabled,nri-prometheus.enabled + name: nri-prometheus + repository: file://./charts/nri-prometheus + version: 2.1.18 +- condition: newrelic-prometheus-agent.enabled + name: newrelic-prometheus-agent + repository: file://./charts/newrelic-prometheus-agent + version: 1.14.3 +- condition: webhook.enabled,nri-metadata-injection.enabled + name: nri-metadata-injection + repository: file://./charts/nri-metadata-injection + version: 4.20.3 +- condition: metrics-adapter.enabled,newrelic-k8s-metrics-adapter.enabled + name: newrelic-k8s-metrics-adapter + repository: file://./charts/newrelic-k8s-metrics-adapter + version: 1.11.1 +- condition: ksm.enabled,kube-state-metrics.enabled + name: kube-state-metrics + repository: file://./charts/kube-state-metrics + version: 5.12.1 +- condition: kubeEvents.enabled,nri-kube-events.enabled + name: nri-kube-events + repository: file://./charts/nri-kube-events + version: 3.10.2 +- condition: logging.enabled,newrelic-logging.enabled + name: newrelic-logging + repository: file://./charts/newrelic-logging + version: 1.22.3 +- condition: newrelic-pixie.enabled + name: newrelic-pixie + repository: file://./charts/newrelic-pixie + version: 2.1.4 +- condition: k8s-agents-operator.enabled + name: k8s-agents-operator + repository: file://./charts/k8s-agents-operator + version: 0.10.0 +- alias: pixie-chart + condition: pixie-chart.enabled + name: pixie-operator-chart + repository: file://./charts/pixie-operator-chart + version: 0.1.6 +- condition: newrelic-infra-operator.enabled + name: newrelic-infra-operator + repository: file://./charts/newrelic-infra-operator + version: 2.11.1 +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.88 diff --git a/charts/new-relic/nri-bundle/5.0.88/README.md b/charts/new-relic/nri-bundle/5.0.88/README.md new file mode 100644 index 000000000..3fcc97d2b --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/README.md.gotmpl b/charts/new-relic/nri-bundle/5.0.88/README.md.gotmpl new file mode 100644 index 000000000..269c4925a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/app-readme.md b/charts/new-relic/nri-bundle/5.0.88/app-readme.md new file mode 100644 index 000000000..61e550787 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/k8s-agents-operator/.helmignore b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/k8s-agents-operator/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/Chart.yaml new file mode 100644 index 000000000..44a9c5d85 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/Chart.yaml @@ -0,0 +1,16 @@ +apiVersion: v2 +appVersion: 0.10.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: juanjjaramillo + url: https://github.com/juanjjaramillo +- name: csongnr + url: https://github.com/csongnr +- name: dbudziwojskiNR + url: https://github.com/dbudziwojskiNR +name: k8s-agents-operator +sources: +- https://github.com/newrelic/k8s-agents-operator +type: application +version: 0.10.0 diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/README.md b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/README.md new file mode 100644 index 000000000..d344832d8 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/README.md @@ -0,0 +1,191 @@ +# k8s-agents-operator + +![Version: 0.10.0](https://img.shields.io/badge/Version-0.10.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.10.0](https://img.shields.io/badge/AppVersion-0.10.0-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 `jetstack` and `k8s-agents-operator` Helm chart repositories: +```shell +helm repo add jetstack https://charts.jetstack.io +helm repo add k8s-agents-operator https://newrelic.github.io/k8s-agents-operator +``` + +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 +``` + +### 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 k8s-agents-operator \ + --create-namespace \ + --values your-custom-values.yaml +``` + +### Monitored namespaces + +For each namespace you want the operator to be instrumented, create a secret containing a valid New Relic ingest license key: +```shell +kubectl create secret generic newrelic-key-secret \ + --namespace my-monitored-namespace \ + --from-literal=new_relic_license_key= +``` + +Similarly, for each namespace you need to instrument create the `Instrumentation` custom resource, specifying which APM agents you want to instrument. All available APM agent docker images and corresponding tags are listed on DockerHub: +* [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) +* [.NET](https://hub.docker.com/repository/docker/newrelic/newrelic-dotnet-init/general) +* [Ruby](https://hub.docker.com/repository/docker/newrelic/newrelic-ruby-init/general) + +```yaml +apiVersion: newrelic.com/v1alpha1 +kind: Instrumentation +metadata: + labels: + app.kubernetes.io/name: instrumentation + app.kubernetes.io/created-by: k8s-agents-operator + name: newrelic-instrumentation +spec: + java: + image: newrelic/newrelic-java-init:latest + # env: + # Example New Relic agent supported environment variables + # - name: NEW_RELIC_LABELS + # value: "environment:auto-injection" + # Example overriding the appName configuration + # - name: NEW_RELIC_POD_NAME + # valueFrom: + # fieldRef: + # fieldPath: metadata.name + # - name: NEW_RELIC_APP_NAME + # value: "$(NEW_RELIC_LABELS)-$(NEW_RELIC_POD_NAME)" + nodejs: + image: newrelic/newrelic-node-init:latest + python: + image: newrelic/newrelic-python-init:latest + dotnet: + image: newrelic/newrelic-dotnet-init:latest + ruby: + image: newrelic/newrelic-ruby-init:latest +``` +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/) + +Global agent settings can be overridden in your deployment manifest if a different configuration is required. + +### Annotations + +The `k8s-agents-operator` looks for language-specific annotations when your pods are being scheduled to know which applications you want to monitor. + +Below are the currently supported annotations: +```yaml +instrumentation.newrelic.com/inject-java: "true" +instrumentation.newrelic.com/inject-nodejs: "true" +instrumentation.newrelic.com/inject-python: "true" +instrumentation.newrelic.com/inject-dotnet: "true" +instrumentation.newrelic.com/inject-ruby: "true" +``` + +Example deployment with annotation to instrument the Java agent: +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spring-petclinic +spec: + selector: + matchLabels: + app: spring-petclinic + replicas: 1 + template: + metadata: + labels: + app: spring-petclinic + annotations: + instrumentation.newrelic.com/inject-java: "true" + spec: + containers: + - name: spring-petclinic + image: ghcr.io/pavolloffay/spring-petclinic:latest + ports: + - containerPort: 8080 + env: + - name: NEW_RELIC_APP_NAME + value: spring-petclinic-demo +``` + +## 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 + +* + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| admissionWebhooks | object | `{"create":true}` | Admission webhooks make sure only requests with correctly formatted rules will get into the Operator | +| 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"` | | +| 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 | +| ---- | ------ | --- | +| juanjjaramillo | | | +| csongnr | | | +| dbudziwojskiNR | | | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.13.1](https://github.com/norwoodj/helm-docs/releases/v1.13.1) diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/README.md.gotmpl b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/README.md.gotmpl new file mode 100644 index 000000000..fadd3b971 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/README.md.gotmpl @@ -0,0 +1,157 @@ +{{ 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 `jetstack` and `k8s-agents-operator` Helm chart repositories: +```shell +helm repo add jetstack https://charts.jetstack.io +helm repo add k8s-agents-operator https://newrelic.github.io/k8s-agents-operator +``` + +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 +``` + +### 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 k8s-agents-operator \ + --create-namespace \ + --values your-custom-values.yaml +``` + +### Monitored namespaces + +For each namespace you want the operator to be instrumented, create a secret containing a valid New Relic ingest license key: +```shell +kubectl create secret generic newrelic-key-secret \ + --namespace my-monitored-namespace \ + --from-literal=new_relic_license_key= +``` + +Similarly, for each namespace you need to instrument create the `Instrumentation` custom resource, specifying which APM agents you want to instrument. All available APM agent docker images and corresponding tags are listed on DockerHub: +* [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) +* [.NET](https://hub.docker.com/repository/docker/newrelic/newrelic-dotnet-init/general) +* [Ruby](https://hub.docker.com/repository/docker/newrelic/newrelic-ruby-init/general) + +```yaml +apiVersion: newrelic.com/v1alpha1 +kind: Instrumentation +metadata: + labels: + app.kubernetes.io/name: instrumentation + app.kubernetes.io/created-by: k8s-agents-operator + name: newrelic-instrumentation +spec: + java: + image: newrelic/newrelic-java-init:latest + # env: + # Example New Relic agent supported environment variables + # - name: NEW_RELIC_LABELS + # value: "environment:auto-injection" + # Example overriding the appName configuration + # - name: NEW_RELIC_POD_NAME + # valueFrom: + # fieldRef: + # fieldPath: metadata.name + # - name: NEW_RELIC_APP_NAME + # value: "$(NEW_RELIC_LABELS)-$(NEW_RELIC_POD_NAME)" + nodejs: + image: newrelic/newrelic-node-init:latest + python: + image: newrelic/newrelic-python-init:latest + dotnet: + image: newrelic/newrelic-dotnet-init:latest + ruby: + image: newrelic/newrelic-ruby-init:latest +``` +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/) + +Global agent settings can be overridden in your deployment manifest if a different configuration is required. + +### Annotations + +The `k8s-agents-operator` looks for language-specific annotations when your pods are being scheduled to know which applications you want to monitor. + +Below are the currently supported annotations: +```yaml +instrumentation.newrelic.com/inject-java: "true" +instrumentation.newrelic.com/inject-nodejs: "true" +instrumentation.newrelic.com/inject-python: "true" +instrumentation.newrelic.com/inject-dotnet: "true" +instrumentation.newrelic.com/inject-ruby: "true" +``` + +Example deployment with annotation to instrument the Java agent: +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spring-petclinic +spec: + selector: + matchLabels: + app: spring-petclinic + replicas: 1 + template: + metadata: + labels: + app: spring-petclinic + annotations: + instrumentation.newrelic.com/inject-java: "true" + spec: + containers: + - name: spring-petclinic + image: ghcr.io/pavolloffay/spring-petclinic:latest + ports: + - containerPort: 8080 + env: + - name: NEW_RELIC_APP_NAME + value: spring-petclinic-demo +``` + +## 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.88/charts/k8s-agents-operator/templates/NOTES.txt b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/NOTES.txt new file mode 100644 index 000000000..e3fb91764 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/NOTES.txt @@ -0,0 +1,36 @@ +This project is currently in experimental phases and is provided AS-IS WITHOUT WARRANTY OR DEDICATED SUPPORT. +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 }} {{ template "k8s-agents-operator.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.88/charts/k8s-agents-operator/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/_helpers.tpl new file mode 100644 index 000000000..43b57a4d4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/_helpers.tpl @@ -0,0 +1,80 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "k8s-agents-operator.name" -}} +{{- .Chart.Name | 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 "k8s-agents-operator.fullname" -}} +{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "k8s-agents-operator.chart" -}} +{{- printf "%s" .Chart.Name | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "k8s-agents-operator.labels" -}} +helm.sh/chart: {{ include "k8s-agents-operator.chart" . }} +{{ include "k8s-agents-operator.selectorLabels" . }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "k8s-agents-operator.selectorLabels" -}} +app.kubernetes.io/name: {{ include "k8s-agents-operator.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "k8s-agents-operator.serviceAccountName" -}} +{{- if .Values.controllerManager.manager.serviceAccount.create }} +{{- default (include "k8s-agents-operator.name" .) .Values.controllerManager.manager.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.controllerManager.manager.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Return the licenseKey +*/}} +{{- define "k8s-agents-operator.licenseKey" -}} +{{- if .Values.global}} + {{- if .Values.global.licenseKey }} + {{- .Values.global.licenseKey -}} + {{- else -}} + {{- .Values.licenseKey | default "" -}} + {{- end -}} +{{- else -}} + {{- .Values.licenseKey | default "" -}} +{{- end -}} +{{- end -}} + +{{/* +Returns if the template should render, it checks if the required values are set. +*/}} +{{- define "k8s-agents-operator.areValuesValid" -}} +{{- $licenseKey := include "k8s-agents-operator.licenseKey" . -}} +{{- and (or $licenseKey)}} +{{- end -}} + +{{/* +Controller manager service certificate's secret. +*/}} +{{- define "k8s-agents-operator.certificateSecret" -}} +{{- printf "%s-controller-manager-service-cert" (include "k8s-agents-operator.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/certmanager.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/certmanager.yaml new file mode 100644 index 000000000..54509f673 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/certmanager.yaml @@ -0,0 +1,17 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ template "k8s-agents-operator.fullname" . }}-serving-cert + labels: + {{- include "k8s-agents-operator.labels" . | nindent 4 }} +spec: + dnsNames: + - '{{ template "k8s-agents-operator.fullname" . }}-webhook-service.{{ .Release.Namespace }}.svc' + - '{{ template "k8s-agents-operator.fullname" . }}-webhook-service.{{ .Release.Namespace }}.svc.{{ .Values.kubernetesClusterDomain }}' + issuerRef: + kind: Issuer + name: '{{ template "k8s-agents-operator.fullname" . }}-selfsigned-issuer' + secretName: {{ template "k8s-agents-operator.certificateSecret" . }} + subject: + organizationalUnits: + - k8s-agents-operator \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/deployment.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/deployment.yaml new file mode 100644 index 000000000..bf19d4e16 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/deployment.yaml @@ -0,0 +1,91 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "k8s-agents-operator.serviceAccountName" . }} + labels: + {{- include "k8s-agents-operator.labels" . | nindent 4 }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "k8s-agents-operator.fullname" . }} + labels: + control-plane: controller-manager + {{- include "k8s-agents-operator.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.controllerManager.replicas }} + selector: + matchLabels: + app.kubernetes.io/name: k8s-agents-operator + control-plane: controller-manager + {{- include "k8s-agents-operator.labels" . | nindent 6 }} + template: + metadata: + labels: + app.kubernetes.io/name: k8s-agents-operator + control-plane: controller-manager + {{- include "k8s-agents-operator.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: 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: {{ template "k8s-agents-operator.serviceAccountName" . }} + terminationGracePeriodSeconds: 10 + {{- if or .Values.admissionWebhooks.create .Values.admissionWebhooks.secretName }} + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: {{ template "k8s-agents-operator.certificateSecret" . }} + {{- end }} + securityContext: +{{ toYaml .Values.securityContext | indent 8 }} diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/instrumentation-crd.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/instrumentation-crd.yaml new file mode 100644 index 000000000..ae81414fb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/instrumentation-crd.yaml @@ -0,0 +1,1150 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: instrumentations.newrelic.com + annotations: + controller-gen.kubebuilder.io/version: v0.11.3 + labels: + {{- include "k8s-agents-operator.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: v1alpha1 + 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: + dotnet: + description: DotNet defines configuration for dotnet auto-instrumentation. + properties: + env: + description: Env defines DotNet specific env 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 DotNet agent and + auto-instrumentation. + type: string + type: object + env: + description: 'Env defines common 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 + exporter: + description: Exporter defines exporter configuration. + properties: + endpoint: + description: Endpoint is address of the collector with OTLP endpoint. + type: string + type: object + go: + description: Go defines configuration for Go auto-instrumentation. + When using Go auto-instrumentation you must provide a value for + the OTEL_GO_AUTO_TARGET_EXE env var via the Instrumentation env + vars or via the instrumentation.opentelemetry.io/otel-go-auto-target-exe + pod annotation. Failure to set this value causes instrumentation + injection to abort, leaving the original pod unchanged. + 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 + 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. + \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." + 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 + java: + description: Java defines configuration for java auto-instrumentation. + properties: + env: + description: Env defines java specific env 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 javaagent auto-instrumentation + JAR. + type: string + type: object + nodejs: + description: NodeJS defines configuration for nodejs auto-instrumentation. + properties: + env: + description: Env defines nodejs specific env 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 NodeJS agent and + auto-instrumentation. + type: string + type: object + php: + description: Php defines configuration for php auto-instrumentation. + properties: + env: + description: Env defines Php specific env 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 Php agent and auto-instrumentation. + type: string + type: object + 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 + python: + description: Python defines configuration for python auto-instrumentation. + properties: + env: + description: Env defines python specific env 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 Python agent and + auto-instrumentation. + type: string + type: object + ruby: + description: Ruby defines configuration for ruby auto-instrumentation. + properties: + env: + description: Env defines Ruby specific env 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 Ruby agent and + auto-instrumentation. + type: string + type: object + 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: null + storedVersions: null \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/leader-election-rbac.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/leader-election-rbac.yaml new file mode 100644 index 000000000..57a5be3a3 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/leader-election-rbac.yaml @@ -0,0 +1,49 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "k8s-agents-operator.fullname" . }}-leader-election-role + labels: + {{- include "k8s-agents-operator.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: {{ template "k8s-agents-operator.fullname" . }}-leader-election-rolebinding + labels: + {{- include "k8s-agents-operator.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: '{{ template "k8s-agents-operator.fullname" . }}-leader-election-role' +subjects: +- kind: ServiceAccount + name: '{{ template "k8s-agents-operator.serviceAccountName" . }}' + namespace: '{{ .Release.Namespace }}' \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/manager-rbac.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/manager-rbac.yaml new file mode 100644 index 000000000..7a1d9d3bf --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/manager-rbac.yaml @@ -0,0 +1,76 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "k8s-agents-operator.fullname" . }}-manager-role + labels: + {{- include "k8s-agents-operator.labels" . | nindent 4 }} +rules: +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- 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: {{ template "k8s-agents-operator.fullname" . }}-manager-rolebinding + labels: + {{- include "k8s-agents-operator.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: '{{ template "k8s-agents-operator.fullname" . }}-manager-role' +subjects: +- kind: ServiceAccount + name: '{{ template "k8s-agents-operator.serviceAccountName" . }}' + namespace: '{{ .Release.Namespace }}' \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/mutating-webhook-configuration.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/mutating-webhook-configuration.yaml new file mode 100644 index 000000000..f37ad6a79 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/mutating-webhook-configuration.yaml @@ -0,0 +1,49 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: {{ template "k8s-agents-operator.fullname" . }}-mutating-webhook-configuration + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ template "k8s-agents-operator.fullname" . }}-serving-cert + labels: + {{- include "k8s-agents-operator.labels" . | nindent 4 }} +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: '{{ template "k8s-agents-operator.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' + path: /mutate-newrelic-com-v1alpha1-instrumentation + failurePolicy: Fail + name: instrumentation.kb.io + rules: + - apiGroups: + - newrelic.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - instrumentations + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: '{{ template "k8s-agents-operator.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' + path: /mutate-v1-pod + failurePolicy: Ignore + name: mpod.kb.io + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - pods + sideEffects: None \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/newrelic_license_secret.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/newrelic_license_secret.yaml new file mode 100644 index 000000000..db2c35f72 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/newrelic_license_secret.yaml @@ -0,0 +1,14 @@ +{{- $licenseKey := include "k8s-agents-operator.licenseKey" . -}} +{{- if $licenseKey }} +apiVersion: v1 +kind: Secret +metadata: + name: "newrelic-key-secret" + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "k8s-agents-operator.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} +type: Opaque +data: + new_relic_license_key: {{ $licenseKey | b64enc }} +{{- end }} \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/proxy-rbac.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/proxy-rbac.yaml new file mode 100644 index 000000000..af583f595 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/proxy-rbac.yaml @@ -0,0 +1,34 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "k8s-agents-operator.fullname" . }}-proxy-role + labels: + {{- include "k8s-agents-operator.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: {{ template "k8s-agents-operator.fullname" . }}-proxy-rolebinding + labels: + {{- include "k8s-agents-operator.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: '{{ template "k8s-agents-operator.fullname" . }}-proxy-role' +subjects: +- kind: ServiceAccount + name: '{{ template "k8s-agents-operator.serviceAccountName" . }}' + namespace: '{{ .Release.Namespace }}' \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/reader-rbac.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/reader-rbac.yaml new file mode 100644 index 000000000..6482ff0db --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/reader-rbac.yaml @@ -0,0 +1,11 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "k8s-agents-operator.fullname" . }}-metrics-reader + labels: + {{- include "k8s-agents-operator.labels" . | nindent 4 }} +rules: +- nonResourceURLs: + - /metrics + verbs: + - get \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/selfsigned-issuer.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/selfsigned-issuer.yaml new file mode 100644 index 000000000..31c0cc79f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/selfsigned-issuer.yaml @@ -0,0 +1,8 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: {{ template "k8s-agents-operator.fullname" . }}-selfsigned-issuer + labels: + {{- include "k8s-agents-operator.labels" . | nindent 4 }} +spec: + selfSigned: {} \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/service.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/service.yaml new file mode 100644 index 000000000..892b1b3e8 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "k8s-agents-operator.fullname" . }} + labels: + control-plane: controller-manager + {{- include "k8s-agents-operator.labels" . | nindent 4 }} +spec: + type: {{ .Values.metricsService.type }} + selector: + app.kubernetes.io/name: {{ include "k8s-agents-operator.chart" . }} + control-plane: controller-manager + {{- include "k8s-agents-operator.labels" . | nindent 4 }} + ports: + {{- .Values.metricsService.ports | toYaml | nindent 2 -}} \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/validating-webhook-configuration.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/validating-webhook-configuration.yaml new file mode 100644 index 000000000..f98608b7e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/validating-webhook-configuration.yaml @@ -0,0 +1,48 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: {{ template "k8s-agents-operator.fullname" . }}-validating-webhook-configuration + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ template "k8s-agents-operator.fullname" . }}-serving-cert + labels: + {{- include "k8s-agents-operator.labels" . | nindent 4 }} +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: '{{ template "k8s-agents-operator.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' + path: /validate-newrelic-com-v1alpha1-instrumentation + failurePolicy: Fail + name: vinstrumentationcreateupdate.kb.io + rules: + - apiGroups: + - newrelic.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - instrumentations + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: '{{ template "k8s-agents-operator.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' + path: /validate-newrelic-com-v1alpha1-instrumentation + failurePolicy: Ignore + name: vinstrumentationdelete.kb.io + rules: + - apiGroups: + - newrelic.com + apiVersions: + - v1alpha1 + operations: + - DELETE + resources: + - instrumentations + sideEffects: None \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/webhook-service.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/webhook-service.yaml new file mode 100644 index 000000000..d2197c679 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/templates/webhook-service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "k8s-agents-operator.fullname" . }}-webhook-service + labels: + {{- include "k8s-agents-operator.labels" . | nindent 4 }} +spec: + type: {{ .Values.webhookService.type }} + selector: + app.kubernetes.io/name: {{ include "k8s-agents-operator.chart" . }} + app.kubernetes.io/name: k8s-agents-operator + control-plane: controller-manager + {{- include "k8s-agents-operator.labels" . | nindent 4 }} + ports: + {{- .Values.webhookService.ports | toYaml | nindent 2 -}} \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/values.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/values.yaml new file mode 100644 index 000000000..7cae82fb8 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/k8s-agents-operator/values.yaml @@ -0,0 +1,62 @@ +# -- Ingest license key to use +# 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 diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/.helmignore b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/.helmignore new file mode 100644 index 000000000..f0c131944 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/kube-state-metrics/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/Chart.yaml new file mode 100644 index 000000000..a86cd07e9 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.10.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.12.1 diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/README.md b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/README.md new file mode 100644 index 000000000..843be89e6 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/kube-state-metrics/templates/NOTES.txt b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/NOTES.txt new file mode 100644 index 000000000..3589c24ec --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/kube-state-metrics/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/_helpers.tpl new file mode 100644 index 000000000..a4358c87a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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 }} +{{ 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.88/charts/kube-state-metrics/templates/ciliumnetworkpolicy.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/ciliumnetworkpolicy.yaml new file mode 100644 index 000000000..025cd47a8 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/kube-state-metrics/templates/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/clusterrolebinding.yaml new file mode 100644 index 000000000..cf9f628d0 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/kube-state-metrics/templates/crs-configmap.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/kube-state-metrics/templates/deployment.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/deployment.yaml new file mode 100644 index 000000000..31aa61018 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/deployment.yaml @@ -0,0 +1,279 @@ +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 }} + 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 }} + {{- if .Values.podAnnotations }} + annotations: +{{ toYaml .Values.podAnnotations | indent 8 }} + {{- end }} + spec: + 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 }} + containers: + {{- $httpPort := 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={{ $httpPort }} + {{- 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 }} + {{- if .Values.customResourceState.enabled }} + - --custom-resource-state-config-file=/etc/customresourcestate/config.yaml + {{- end }} + {{- 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 }} + livenessProbe: + httpGet: + path: /healthz + port: {{ $httpPort }} + initialDelaySeconds: 5 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: / + port: {{ $httpPort }} + initialDelaySeconds: 5 + timeoutSeconds: 5 + {{- if .Values.resources }} + resources: +{{ toYaml .Values.resources | indent 10 }} +{{- end }} +{{- 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:{{ $httpPort }}/ + - --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 }} +{{- 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 }} + {{- if .Values.nodeSelector }} + nodeSelector: +{{ toYaml .Values.nodeSelector | indent 8 }} + {{- end }} + {{- if .Values.tolerations }} + tolerations: +{{ toYaml .Values.tolerations | 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.88/charts/kube-state-metrics/templates/extra-manifests.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/kube-state-metrics/templates/kubeconfig-secret.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/kube-state-metrics/templates/networkpolicy.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/networkpolicy.yaml new file mode 100644 index 000000000..309b38ec5 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/kube-state-metrics/templates/pdb.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/pdb.yaml new file mode 100644 index 000000000..3771b511d --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/kube-state-metrics/templates/podsecuritypolicy.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/podsecuritypolicy.yaml new file mode 100644 index 000000000..8905e113e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/kube-state-metrics/templates/psp-clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/kube-state-metrics/templates/psp-clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/kube-state-metrics/templates/rbac-configmap.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/kube-state-metrics/templates/role.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/role.yaml new file mode 100644 index 000000000..d33687f2d --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/kube-state-metrics/templates/rolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/rolebinding.yaml new file mode 100644 index 000000000..330651b73 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/kube-state-metrics/templates/service.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/service.yaml new file mode 100644 index 000000000..6c486a662 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/service.yaml @@ -0,0 +1,49 @@ +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 }}" + 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.88/charts/kube-state-metrics/templates/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/serviceaccount.yaml new file mode 100644 index 000000000..a7ff4dd3d --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/serviceaccount.yaml @@ -0,0 +1,15 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +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 }} +imagePullSecrets: + {{- include "kube-state-metrics.imagePullSecrets" (dict "Values" .Values "imagePullSecrets" .Values.serviceAccount.imagePullSecrets) | indent 2 }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/servicemonitor.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/servicemonitor.yaml new file mode 100644 index 000000000..79a07a655 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/servicemonitor.yaml @@ -0,0 +1,114 @@ +{{- 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 }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.prometheus.monitor.annotations }} + annotations: + {{- 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 .Values.prometheus.monitor.interval }} + interval: {{ .Values.prometheus.monitor.interval }} + {{- end }} + {{- if .Values.prometheus.monitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.prometheus.monitor.scrapeTimeout }} + {{- end }} + {{- if .Values.prometheus.monitor.proxyUrl }} + proxyUrl: {{ .Values.prometheus.monitor.proxyUrl}} + {{- end }} + {{- if .Values.prometheus.monitor.honorLabels }} + honorLabels: true + {{- end }} + {{- if .Values.prometheus.monitor.metricRelabelings }} + metricRelabelings: + {{- toYaml .Values.prometheus.monitor.metricRelabelings | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.monitor.relabelings }} + relabelings: + {{- toYaml .Values.prometheus.monitor.relabelings | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.monitor.scheme }} + scheme: {{ .Values.prometheus.monitor.scheme }} + {{- end }} + {{- if .Values.prometheus.monitor.tlsConfig }} + tlsConfig: + {{- toYaml .Values.prometheus.monitor.tlsConfig | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.monitor.bearerTokenFile }} + bearerTokenFile: {{ .Values.prometheus.monitor.bearerTokenFile }} + {{- end }} + {{- with .Values.prometheus.monitor.bearerTokenSecret }} + bearerTokenSecret: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.selfMonitor.enabled }} + - port: metrics + {{- if .Values.prometheus.monitor.interval }} + interval: {{ .Values.prometheus.monitor.interval }} + {{- end }} + {{- if .Values.prometheus.monitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.prometheus.monitor.scrapeTimeout }} + {{- end }} + {{- if .Values.prometheus.monitor.proxyUrl }} + proxyUrl: {{ .Values.prometheus.monitor.proxyUrl}} + {{- end }} + {{- if .Values.prometheus.monitor.honorLabels }} + honorLabels: true + {{- end }} + {{- if .Values.prometheus.monitor.metricRelabelings }} + metricRelabelings: + {{- toYaml .Values.prometheus.monitor.metricRelabelings | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.monitor.relabelings }} + relabelings: + {{- toYaml .Values.prometheus.monitor.relabelings | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.monitor.scheme }} + scheme: {{ .Values.prometheus.monitor.scheme }} + {{- end }} + {{- if .Values.prometheus.monitor.tlsConfig }} + tlsConfig: + {{- toYaml .Values.prometheus.monitor.tlsConfig | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.monitor.bearerTokenFile }} + bearerTokenFile: {{ .Values.prometheus.monitor.bearerTokenFile }} + {{- end }} + {{- with .Values.prometheus.monitor.bearerTokenSecret }} + bearerTokenSecret: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/stsdiscovery-role.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/kube-state-metrics/templates/stsdiscovery-rolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/kube-state-metrics/templates/verticalpodautoscaler.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/templates/verticalpodautoscaler.yaml new file mode 100644 index 000000000..f46305b51 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/kube-state-metrics/values.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/values.yaml new file mode 100644 index 000000000..00eabab6a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/kube-state-metrics/values.yaml @@ -0,0 +1,441 @@ +# 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 + +# 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: [] + +service: + port: 8080 + # Default to clusterIP for backward compatibility + type: ClusterIP + 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.14.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: {} + + 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: {} + +prometheus: + monitor: + enabled: false + annotations: {} + additionalLabels: {} + namespace: "" + namespaceSelector: [] + jobLabel: "" + targetLabels: [] + podTargetLabels: [] + interval: "" + ## 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 + scrapeTimeout: "" + proxyUrl: "" + selectorOverride: {} + 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: + 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: {} + +## 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" diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/.helmignore b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/.helmignore new file mode 100644 index 000000000..f62b5519e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infra-operator/Chart.lock b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/Chart.lock new file mode 100644 index 000000000..ccd9266c2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/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-06-21T17:38:34.069969308Z" diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/Chart.yaml new file mode 100644 index 000000000..bf3a0b208 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/Chart.yaml @@ -0,0 +1,35 @@ +apiVersion: v2 +appVersion: 0.19.1 +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.2.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.11.1 diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/README.md b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/README.md new file mode 100644 index 000000000..05e8a8d48 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infra-operator/README.md.gotmpl b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/README.md.gotmpl new file mode 100644 index 000000000..1ef603355 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infra-operator/charts/common-library/.helmignore b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/Chart.yaml new file mode 100644 index 000000000..b65ac15d4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.2.0 diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/DEVELOPERS.md b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/DEVELOPERS.md new file mode 100644 index 000000000..3ccc108e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/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.88/charts/newrelic-infra-operator/charts/common-library/README.md b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_affinity.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_agent-config.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_cluster.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_custom-attributes.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_dnsconfig.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_fedramp.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_hostnetwork.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_images.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_insights.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_insights_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_labels.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_license.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/charts/common-library/templates/_license.tpl new file mode 100644 index 000000000..647b4ff43 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_license_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_low-data-mode.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_naming.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_nodeselector.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_priority-class-name.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_privileged.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_proxy.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_security-context.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_serviceaccount.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_staging.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_tolerations.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/templates/_verbose-log.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/charts/common-library/values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/ci/test-values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/templates/NOTES.txt b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/NOTES.txt new file mode 100644 index 000000000..5b11d2d83 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infra-operator/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/_helpers.tpl new file mode 100644 index 000000000..8a8858c82 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/job-createSecret.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/job-patchWebhook.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/psp.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/role.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/rolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/templates/admission-webhooks/mutatingWebhookConfiguration.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/templates/cert-manager.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/templates/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/clusterrole.yaml new file mode 100644 index 000000000..cb20e310d --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infra-operator/templates/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/clusterrolebinding.yaml new file mode 100644 index 000000000..1f5f8b89b --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infra-operator/templates/configmap.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/configmap.yaml new file mode 100644 index 000000000..fdb4a1e3b --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infra-operator/templates/deployment.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/deployment.yaml new file mode 100644 index 000000000..40f389887 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infra-operator/templates/secret.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/secret.yaml new file mode 100644 index 000000000..f558ee86c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infra-operator/templates/service.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/service.yaml new file mode 100644 index 000000000..04af4d09c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infra-operator/templates/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/templates/serviceaccount.yaml new file mode 100644 index 000000000..b1e74523e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infra-operator/tests/deployment_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/tests/job_patch_psp_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/tests/job_serviceaccount_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/tests/rbac_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infra-operator/values.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infra-operator/values.yaml new file mode 100644 index 000000000..3dd6fd055 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/.helmignore b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/.helmignore new file mode 100644 index 000000000..2bfa6a4d9 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/.helmignore @@ -0,0 +1 @@ +tests/ diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/Chart.lock b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/Chart.lock new file mode 100644 index 000000000..e921cc491 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/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-06-24T12:11:50.10851808Z" diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/Chart.yaml new file mode 100644 index 000000000..894f2f81c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/Chart.yaml @@ -0,0 +1,26 @@ +apiVersion: v2 +appVersion: 3.29.3 +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.2.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.34.3 diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/README.md b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/README.md new file mode 100644 index 000000000..923b6109b --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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 master +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 master 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.88/charts/newrelic-infrastructure/README.md.gotmpl b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/README.md.gotmpl new file mode 100644 index 000000000..84f2f9083 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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 master +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.88/charts/newrelic-infrastructure/charts/common-library/.helmignore b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/charts/common-library/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/Chart.yaml new file mode 100644 index 000000000..b65ac15d4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.2.0 diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/DEVELOPERS.md b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/DEVELOPERS.md new file mode 100644 index 000000000..3ccc108e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/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.88/charts/newrelic-infrastructure/charts/common-library/README.md b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_affinity.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_agent-config.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_cluster.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_custom-attributes.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_dnsconfig.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_fedramp.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_hostnetwork.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_images.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_insights.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_insights_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_labels.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_license.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/charts/common-library/templates/_license.tpl new file mode 100644 index 000000000..647b4ff43 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_license_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_low-data-mode.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_naming.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_nodeselector.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_priority-class-name.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_privileged.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_proxy.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_security-context.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_serviceaccount.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_staging.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_tolerations.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/templates/_verbose-log.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/charts/common-library/values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/ci/test-cplane-kind-deployment-values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/ci/test-values.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/ci/test-values.yaml new file mode 100644 index 000000000..125a49607 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/templates/NOTES.txt b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/NOTES.txt new file mode 100644 index 000000000..16cc6ea13 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/_helpers.tpl new file mode 100644 index 000000000..033ef0bfc --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/templates/_helpers_compatibility.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/_helpers_compatibility.tpl new file mode 100644 index 000000000..07365e5a1 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/templates/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/clusterrole.yaml new file mode 100644 index 000000000..391dc1e1f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/templates/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/clusterrolebinding.yaml new file mode 100644 index 000000000..fc5dfb8da --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/templates/controlplane/_affinity_helper.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/controlplane/_agent-config_helper.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/controlplane/_host_network.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/controlplane/_naming.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/_naming.tpl new file mode 100644 index 000000000..4b9ef22e3 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/templates/controlplane/_rbac.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/_rbac.tpl new file mode 100644 index 000000000..a279df6b4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/templates/controlplane/_tolerations_helper.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/controlplane/agent-configmap.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/controlplane/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/clusterrole.yaml new file mode 100644 index 000000000..57633e7f7 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/templates/controlplane/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/clusterrolebinding.yaml new file mode 100644 index 000000000..4e3530094 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/templates/controlplane/daemonset.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/daemonset.yaml new file mode 100644 index 000000000..938fc48d4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/templates/controlplane/rolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/rolebinding.yaml new file mode 100644 index 000000000..d97fc181a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/templates/controlplane/scraper-configmap.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/controlplane/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/controlplane/serviceaccount.yaml new file mode 100644 index 000000000..502e1c986 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/templates/ksm/_affinity_helper.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/ksm/_agent-config_helper.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/ksm/_host_network.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/ksm/_naming.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/ksm/_naming.tpl new file mode 100644 index 000000000..d8c283c43 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/templates/ksm/_tolerations_helper.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/ksm/agent-configmap.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/ksm/deployment.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/ksm/deployment.yaml new file mode 100644 index 000000000..507199d5a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/templates/ksm/scraper-configmap.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/kubelet/_affinity_helper.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/kubelet/_agent-config_helper.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/kubelet/_host_network.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/kubelet/_naming.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/kubelet/_naming.tpl new file mode 100644 index 000000000..71c142156 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/templates/kubelet/_security_context_helper.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/kubelet/_tolerations_helper.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/kubelet/agent-configmap.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/kubelet/daemonset.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/kubelet/daemonset.yaml new file mode 100644 index 000000000..517079be7 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/templates/kubelet/integrations-configmap.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/kubelet/scraper-configmap.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-infrastructure/templates/podsecuritypolicy.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/podsecuritypolicy.yaml new file mode 100644 index 000000000..5b5058511 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/templates/secret.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/secret.yaml new file mode 100644 index 000000000..f558ee86c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/templates/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/templates/serviceaccount.yaml new file mode 100644 index 000000000..f987cc512 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-infrastructure/values.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/values.yaml new file mode 100644 index 000000000..665f1fe69 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-infrastructure/values.yaml @@ -0,0 +1,602 @@ +# -- 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.55.1 + pullPolicy: IfNotPresent + # -- Image for the New Relic Infrastructure Agent plus integrations. + # @default -- See `values.yaml` + agent: + registry: "" + repository: newrelic/infrastructure-bundle + tag: 3.2.48 + 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 master 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 + - matchExpressions: + - key: node-role.kubernetes.io/master + 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.88/charts/newrelic-k8s-metrics-adapter/.helmignore b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/.helmignore new file mode 100644 index 000000000..1ed4e226e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-k8s-metrics-adapter/Chart.lock b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/Chart.lock new file mode 100644 index 000000000..fc2918195 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/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-06-22T03:37:58.957965768Z" diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/Chart.yaml new file mode 100644 index 000000000..4c027ae58 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/Chart.yaml @@ -0,0 +1,25 @@ +apiVersion: v2 +appVersion: 0.13.1 +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.2.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.11.1 diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/README.md b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/README.md new file mode 100644 index 000000000..9f3943ec4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.2.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.88/charts/newrelic-k8s-metrics-adapter/README.md.gotmpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/.helmignore b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/Chart.yaml new file mode 100644 index 000000000..b65ac15d4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.2.0 diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/DEVELOPERS.md b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/DEVELOPERS.md new file mode 100644 index 000000000..3ccc108e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/README.md b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_affinity.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_agent-config.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_cluster.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_custom-attributes.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_dnsconfig.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_fedramp.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_hostnetwork.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_images.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_insights.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_insights_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_labels.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_license.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_license.tpl new file mode 100644 index 000000000..647b4ff43 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_license_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_low-data-mode.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_naming.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_nodeselector.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_priority-class-name.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_privileged.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_proxy.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_security-context.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_serviceaccount.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_staging.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_tolerations.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_verbose-log.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/charts/common-library/values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/ci/test-values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/templates/adapter-clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/templates/adapter-rolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/templates/apiservice/apiservice.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/job-createSecret.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/job-patchAPIService.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/psp.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/role.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/rolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/templates/configmap.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/templates/deployment.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/templates/hpa-clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/templates/hpa-clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/templates/secret.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/templates/service.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/templates/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/tests/apiservice_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/tests/common_extra_naming_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/tests/configmap_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/tests/deployment_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/tests/hpa_clusterrolebinding_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/tests/job_patch_cluster_rolebinding_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/tests/job_patch_clusterrole_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/tests/job_patch_common_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/tests/job_patch_job_createsecret_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/tests/job_patch_job_patchapiservice_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/tests/job_serviceaccount_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/tests/rbac_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-k8s-metrics-adapter/values.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-k8s-metrics-adapter/values.yaml new file mode 100644 index 000000000..5c610f792 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-logging/Chart.lock b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/Chart.lock new file mode 100644 index 000000000..064abf8aa --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-logging/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/Chart.yaml new file mode 100644 index 000000000..d96fc32c8 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/Chart.yaml @@ -0,0 +1,20 @@ +apiVersion: v2 +appVersion: 2.0.0 +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.22.3 diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/README.md b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/README.md new file mode 100644 index 000000000..2f7e4e853 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/README.md @@ -0,0 +1,267 @@ +# 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. | | +| `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.88/charts/newrelic-logging/charts/common-library/.helmignore b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/charts/common-library/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-logging/charts/common-library/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/DEVELOPERS.md b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/README.md b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_affinity.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_agent-config.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_cluster.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_custom-attributes.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_dnsconfig.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_fedramp.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_hostnetwork.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_images.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_insights.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_insights_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_labels.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_license.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_license_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_low-data-mode.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_naming.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_nodeselector.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_priority-class-name.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_privileged.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_proxy.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_security-context.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_serviceaccount.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_staging.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_tolerations.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/templates/_verbose-log.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/charts/common-library/values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/ci/test-enable-windows-values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/ci/test-lowdatamode-values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-logging/ci/test-lowdatamode-values.yaml @@ -0,0 +1 @@ +lowDataMode: true diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/ci/test-override-global-lowdatamode.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/ci/test-staging-values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-logging/ci/test-staging-values.yaml @@ -0,0 +1 @@ +nrStaging: true diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/ci/test-with-empty-global.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-logging/ci/test-with-empty-global.yaml @@ -0,0 +1 @@ +global: {} diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/ci/test-with-empty-values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-logging/fluent-bit-and-plugin-metrics-dashboard-template.json b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/templates/NOTES.txt b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/NOTES.txt new file mode 100644 index 000000000..289f2157f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-logging/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/_helpers.tpl new file mode 100644 index 000000000..439d25cae --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-logging/templates/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/clusterrole.yaml new file mode 100644 index 000000000..b36340fe6 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-logging/templates/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/clusterrolebinding.yaml new file mode 100644 index 000000000..6b258f697 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-logging/templates/configmap.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/configmap.yaml new file mode 100644 index 000000000..4b1d89014 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-logging/templates/daemonset-windows.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/daemonset-windows.yaml new file mode 100644 index 000000000..a4a9e08fc --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/daemonset-windows.yaml @@ -0,0 +1,171 @@ +{{- 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.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.88/charts/newrelic-logging/templates/daemonset.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/daemonset.yaml new file mode 100644 index 000000000..1bc779a99 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/daemonset.yaml @@ -0,0 +1,208 @@ +{{- 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 }} + 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 }} + {{- 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: 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: + - /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.88/charts/newrelic-logging/templates/persistentvolume.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/persistentvolume.yaml new file mode 100644 index 000000000..f2fb93d77 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-logging/templates/podsecuritypolicy.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/podsecuritypolicy.yaml new file mode 100644 index 000000000..2c8c598e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-logging/templates/secret.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/secret.yaml new file mode 100644 index 000000000..47a56e573 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-logging/templates/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/templates/serviceaccount.yaml new file mode 100644 index 000000000..51da56a3e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-logging/tests/cri_parser_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/tests/dns_config_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/tests/endpoint_region_selection_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/tests/fluentbit_k8logging_exclude_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/tests/fluentbit_persistence_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/tests/fluentbit_pod_label_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/tests/fluentbit_sendmetrics_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/tests/images_test.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/tests/images_test.yaml new file mode 100644 index 000000000..3dc5b7a4e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.0 + template: templates/daemonset.yaml + - equal: + path: spec.template.spec.containers[0].image + value: newrelic/newrelic-fluentbit-output:2.0.0-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.0-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.88/charts/newrelic-logging/tests/linux_volume_mount_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-logging/tests/rbac_test.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/tests/rbac_test.yaml new file mode 100644 index 000000000..a8d85da98 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-logging/values.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/values.yaml new file mode 100644 index 000000000..991f9ee9c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-logging/values.yaml @@ -0,0 +1,357 @@ +# 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: {} + +# 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.88/charts/newrelic-pixie/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/Chart.yaml new file mode 100644 index 000000000..acd3077d4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +appVersion: 2.1.4 +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.4 diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/README.md b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/README.md new file mode 100644 index 000000000..228a3676d --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-pixie/ci/test-values.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/ci/test-values.yaml new file mode 100644 index 000000000..580f9b0ba --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-pixie/templates/NOTES.txt b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/templates/NOTES.txt new file mode 100644 index 000000000..d54283889 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-pixie/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/templates/_helpers.tpl new file mode 100644 index 000000000..40b9c68df --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-pixie/templates/configmap.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/templates/configmap.yaml new file mode 100644 index 000000000..19f7fe61a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-pixie/templates/job.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/templates/job.yaml new file mode 100644 index 000000000..89b97514f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/templates/job.yaml @@ -0,0 +1,164 @@ +{{- 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: gcr.io/pixie-oss/pixie-dev-public/curl:1.0 + 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.88/charts/newrelic-pixie/templates/secret.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/templates/secret.yaml new file mode 100644 index 000000000..4d9561877 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-pixie/tests/configmap.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/tests/configmap.yaml new file mode 100644 index 000000000..ecba6363b --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-pixie/tests/jobs.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/tests/jobs.yaml new file mode 100644 index 000000000..03a3d86b8 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-pixie/values.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/values.yaml new file mode 100644 index 000000000..4103d54e9 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-pixie/values.yaml @@ -0,0 +1,70 @@ +# 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: + +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.88/charts/newrelic-prometheus-agent/.helmignore b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-prometheus-agent/Chart.lock b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/Chart.lock new file mode 100644 index 000000000..18bbb9ef4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/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-06-21T18:14:01.260095101Z" diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/Chart.yaml new file mode 100644 index 000000000..08d0cf6dc --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/Chart.yaml @@ -0,0 +1,22 @@ +annotations: + configuratorVersion: 1.17.3 +apiVersion: v2 +appVersion: v2.37.8 +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.2.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.14.3 diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/README.md b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/README.md new file mode 100644 index 000000000..069b9a79b --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-prometheus-agent/README.md.gotmpl b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/README.md.gotmpl new file mode 100644 index 000000000..8738b7329 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/.helmignore b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/Chart.yaml new file mode 100644 index 000000000..b65ac15d4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.2.0 diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/DEVELOPERS.md b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/DEVELOPERS.md new file mode 100644 index 000000000..3ccc108e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/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.88/charts/newrelic-prometheus-agent/charts/common-library/README.md b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_affinity.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_agent-config.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_cluster.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_custom-attributes.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_dnsconfig.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_fedramp.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_hostnetwork.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_images.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_insights.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_insights_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_labels.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_license.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_license.tpl new file mode 100644 index 000000000..647b4ff43 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_license_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_low-data-mode.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_naming.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_nodeselector.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_priority-class-name.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_privileged.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_proxy.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_security-context.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_serviceaccount.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_staging.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_tolerations.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/templates/_verbose-log.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/charts/common-library/values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/ci/test-values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/static/lowdatamodedefaults.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/static/lowdatamodedefaults.yaml new file mode 100644 index 000000000..726815755 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-prometheus-agent/static/metrictyperelabeldefaults.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/static/metrictyperelabeldefaults.yaml new file mode 100644 index 000000000..c0a277409 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-prometheus-agent/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/templates/_helpers.tpl new file mode 100644 index 000000000..6cc58e251 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-prometheus-agent/templates/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/templates/clusterrole.yaml new file mode 100644 index 000000000..e9d4208e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-prometheus-agent/templates/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/templates/clusterrolebinding.yaml new file mode 100644 index 000000000..44244653f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-prometheus-agent/templates/configmap.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/templates/configmap.yaml new file mode 100644 index 000000000..b775aca74 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-prometheus-agent/templates/secret.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/templates/secret.yaml new file mode 100644 index 000000000..f558ee86c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-prometheus-agent/templates/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/templates/serviceaccount.yaml new file mode 100644 index 000000000..b1e74523e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-prometheus-agent/templates/statefulset.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/templates/statefulset.yaml new file mode 100644 index 000000000..846c41c23 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/newrelic-prometheus-agent/tests/configmap_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/tests/configurator_image_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/tests/integration_filters_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/tests/lowdatamode_configmap_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/newrelic-prometheus-agent/values.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/newrelic-prometheus-agent/values.yaml new file mode 100644 index 000000000..2fb3ed7bc --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-kube-events/Chart.lock b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/Chart.lock new file mode 100644 index 000000000..dff6eab20 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/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-06-21T19:47:28.685291839Z" diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/Chart.yaml new file mode 100644 index 000000000..b90689962 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/Chart.yaml @@ -0,0 +1,26 @@ +apiVersion: v2 +appVersion: 2.10.2 +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.2.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.10.2 diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/README.md b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/README.md new file mode 100644 index 000000000..2f9daeb4b --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/README.md @@ -0,0 +1,79 @@ +# nri-kube-events + +![Version: 3.10.2](https://img.shields.io/badge/Version-3.10.2-informational?style=flat-square) ![AppVersion: 2.10.2](https://img.shields.io/badge/AppVersion-2.10.2-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.88/charts/nri-kube-events/README.md.gotmpl b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/README.md.gotmpl new file mode 100644 index 000000000..e77eb7f14 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-kube-events/charts/common-library/.helmignore b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/Chart.yaml new file mode 100644 index 000000000..b65ac15d4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.2.0 diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/DEVELOPERS.md b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/DEVELOPERS.md new file mode 100644 index 000000000..3ccc108e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/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.88/charts/nri-kube-events/charts/common-library/README.md b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_affinity.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_agent-config.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_cluster.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_custom-attributes.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_dnsconfig.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_fedramp.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_hostnetwork.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_images.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_insights.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_insights_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_labels.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_license.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/charts/common-library/templates/_license.tpl new file mode 100644 index 000000000..647b4ff43 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/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.88/charts/nri-kube-events/charts/common-library/templates/_license_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_low-data-mode.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_naming.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_nodeselector.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_priority-class-name.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_privileged.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_proxy.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_security-context.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_serviceaccount.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_staging.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_tolerations.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/templates/_verbose-log.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/charts/common-library/values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/ci/test-bare-minimum-values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/ci/test-custom-attributes-as-map.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/ci/test-custom-attributes-as-string.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/ci/test-values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/templates/NOTES.txt b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/templates/NOTES.txt new file mode 100644 index 000000000..3fd06b4a2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-kube-events/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/templates/_helpers.tpl new file mode 100644 index 000000000..5d0b8d257 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-kube-events/templates/_helpers_compatibility.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/templates/agent-configmap.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/templates/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/templates/clusterrole.yaml new file mode 100644 index 000000000..cbfd5d9ce --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-kube-events/templates/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/templates/clusterrolebinding.yaml new file mode 100644 index 000000000..fc5dfb8da --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-kube-events/templates/configmap.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/templates/configmap.yaml new file mode 100644 index 000000000..9e4e35f6b --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-kube-events/templates/deployment.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/templates/deployment.yaml new file mode 100644 index 000000000..7ba9eaea9 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-kube-events/templates/secret.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/templates/secret.yaml new file mode 100644 index 000000000..f558ee86c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-kube-events/templates/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/templates/serviceaccount.yaml new file mode 100644 index 000000000..07e818da0 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-kube-events/tests/agent_configmap_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/tests/configmap_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/tests/deployment_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/tests/images_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/tests/security_context_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-kube-events/values.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-kube-events/values.yaml new file mode 100644 index 000000000..6cd4583c6 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.55.1 + 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.88/charts/nri-metadata-injection/.helmignore b/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/.helmignore new file mode 100644 index 000000000..f62b5519e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-metadata-injection/Chart.lock b/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/Chart.lock new file mode 100644 index 000000000..c65e88efd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/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-06-21T17:31:31.266100576Z" diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/Chart.yaml new file mode 100644 index 000000000..2cd888093 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/Chart.yaml @@ -0,0 +1,25 @@ +apiVersion: v2 +appVersion: 1.28.3 +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.2.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.20.3 diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/README.md b/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/README.md new file mode 100644 index 000000000..dd922ef13 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-metadata-injection/README.md.gotmpl b/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/README.md.gotmpl new file mode 100644 index 000000000..752ba8aae --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-metadata-injection/charts/common-library/.helmignore b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/Chart.yaml new file mode 100644 index 000000000..b65ac15d4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.2.0 diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/DEVELOPERS.md b/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/DEVELOPERS.md new file mode 100644 index 000000000..3ccc108e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/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.88/charts/nri-metadata-injection/charts/common-library/README.md b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_affinity.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_agent-config.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_cluster.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_custom-attributes.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_dnsconfig.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_fedramp.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_hostnetwork.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_images.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_insights.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_insights_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_labels.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_license.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/charts/common-library/templates/_license.tpl new file mode 100644 index 000000000..647b4ff43 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/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.88/charts/nri-metadata-injection/charts/common-library/templates/_license_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_low-data-mode.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_naming.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_nodeselector.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_priority-class-name.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_privileged.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_proxy.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_security-context.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_serviceaccount.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_staging.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_tolerations.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/templates/_verbose-log.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/charts/common-library/values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/ci/test-values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/templates/NOTES.txt b/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/templates/NOTES.txt new file mode 100644 index 000000000..544124d11 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-metadata-injection/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/templates/_helpers.tpl new file mode 100644 index 000000000..54a23e981 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/job-createSecret.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/job-patchWebhook.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/psp.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/psp.yaml new file mode 100644 index 000000000..20cf0e3bd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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)) }} +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.88/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/role.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/rolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/templates/admission-webhooks/mutatingWebhookConfiguration.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/templates/cert-manager.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/templates/deployment.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/templates/deployment.yaml new file mode 100644 index 000000000..4974dbbc1 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-metadata-injection/templates/service.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/templates/service.yaml new file mode 100644 index 000000000..e4a57587c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-metadata-injection/tests/cluster_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/tests/job_serviceaccount_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/tests/rbac_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/tests/volume_mounts_test.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-metadata-injection/values.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-metadata-injection/values.yaml new file mode 100644 index 000000000..849135c35 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.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 + +# -- 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.88/charts/nri-prometheus/.helmignore b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/.helmignore new file mode 100644 index 000000000..50af03172 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-prometheus/Chart.lock b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/Chart.lock new file mode 100644 index 000000000..13fed1c85 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/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-15T13:04:29.3144+02:00" diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/Chart.yaml new file mode 100644 index 000000000..9f4153dab --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.2.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.18 diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/README.md b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/README.md new file mode 100644 index 000000000..0287b2b2a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-prometheus/README.md.gotmpl b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/README.md.gotmpl new file mode 100644 index 000000000..5c1da4577 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-prometheus/charts/common-library/.helmignore b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-prometheus/charts/common-library/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/Chart.yaml new file mode 100644 index 000000000..b65ac15d4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.2.0 diff --git a/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/DEVELOPERS.md b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/DEVELOPERS.md new file mode 100644 index 000000000..3ccc108e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/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.88/charts/nri-prometheus/charts/common-library/README.md b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_affinity.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_agent-config.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_cluster.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_custom-attributes.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_dnsconfig.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_fedramp.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_hostnetwork.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_images.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_insights.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_insights_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_labels.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_license.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/charts/common-library/templates/_license.tpl new file mode 100644 index 000000000..647b4ff43 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/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.88/charts/nri-prometheus/charts/common-library/templates/_license_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_low-data-mode.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_naming.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_nodeselector.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_priority-class-name.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_privileged.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_proxy.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_security-context.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_serviceaccount.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_staging.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_tolerations.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/templates/_verbose-log.tpl b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/charts/common-library/values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/ci/test-lowdatamode-values.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/ci/test-override-global-lowdatamode.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/nri-prometheus/ci/test-values.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/ci/test-values.yaml new file mode 100644 index 000000000..fcd07b2d3 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-prometheus/static/lowdatamodedefaults.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/static/lowdatamodedefaults.yaml new file mode 100644 index 000000000..f749e28da --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-prometheus/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/templates/_helpers.tpl new file mode 100644 index 000000000..23c072bd7 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-prometheus/templates/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/templates/clusterrole.yaml new file mode 100644 index 000000000..ac4734d31 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-prometheus/templates/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/templates/clusterrolebinding.yaml new file mode 100644 index 000000000..44244653f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-prometheus/templates/configmap.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/templates/configmap.yaml new file mode 100644 index 000000000..5daeed64a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-prometheus/templates/deployment.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/templates/deployment.yaml new file mode 100644 index 000000000..8529b71f4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-prometheus/templates/secret.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/templates/secret.yaml new file mode 100644 index 000000000..f558ee86c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-prometheus/templates/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/templates/serviceaccount.yaml new file mode 100644 index 000000000..df451ec90 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-prometheus/tests/configmap_test.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/tests/configmap_test.yaml new file mode 100644 index 000000000..ae7d921fe --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-prometheus/tests/deployment_test.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/tests/deployment_test.yaml new file mode 100644 index 000000000..cb6f90340 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-prometheus/tests/labels_test.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/tests/labels_test.yaml new file mode 100644 index 000000000..2b6cb53bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/nri-prometheus/values.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/nri-prometheus/values.yaml new file mode 100644 index 000000000..4c562cc66 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/pixie-operator-chart/Chart.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/pixie-operator-chart/Chart.yaml new file mode 100644 index 000000000..a0ce0a388 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/pixie-operator-chart/crds/olm_crd.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/pixie-operator-chart/crds/vizier_crd.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/pixie-operator-chart/templates/00_olm.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/pixie-operator-chart/templates/01_px_olm.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/pixie-operator-chart/templates/02_catalog.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/pixie-operator-chart/templates/03_subscription.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/pixie-operator-chart/templates/04_vizier.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/pixie-operator-chart/templates/deleter.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/pixie-operator-chart/templates/deleter.yaml new file mode 100644 index 000000000..b1cde0c92 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/charts/pixie-operator-chart/templates/deleter_role.yaml b/charts/new-relic/nri-bundle/5.0.88/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.88/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.88/charts/pixie-operator-chart/values.yaml b/charts/new-relic/nri-bundle/5.0.88/charts/pixie-operator-chart/values.yaml new file mode 100644 index 000000000..a3ffe7c9d --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/ci/test-values.yaml b/charts/new-relic/nri-bundle/5.0.88/ci/test-values.yaml new file mode 100644 index 000000000..7ba6c8c32 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/questions.yaml b/charts/new-relic/nri-bundle/5.0.88/questions.yaml new file mode 100644 index 000000000..de3fa9fea --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.88/values.yaml b/charts/new-relic/nri-bundle/5.0.88/values.yaml new file mode 100644 index 000000000..47c58df8e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.88/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.231/.helmignore b/charts/speedscale/speedscale-operator/2.2.231/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.231/.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.231/Chart.yaml b/charts/speedscale/speedscale-operator/2.2.231/Chart.yaml new file mode 100644 index 000000000..1d289ca58 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.231/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.231 +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.231 diff --git a/charts/speedscale/speedscale-operator/2.2.231/LICENSE b/charts/speedscale/speedscale-operator/2.2.231/LICENSE new file mode 100644 index 000000000..b78723d62 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.231/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.231/README.md b/charts/speedscale/speedscale-operator/2.2.231/README.md new file mode 100644 index 000000000..97a522e89 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.231/README.md @@ -0,0 +1,108 @@ +# 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.231/app-readme.md b/charts/speedscale/speedscale-operator/2.2.231/app-readme.md new file mode 100644 index 000000000..97a522e89 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.231/app-readme.md @@ -0,0 +1,108 @@ +# 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.231/questions.yaml b/charts/speedscale/speedscale-operator/2.2.231/questions.yaml new file mode 100644 index 000000000..29aee3895 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.231/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.231/templates/NOTES.txt b/charts/speedscale/speedscale-operator/2.2.231/templates/NOTES.txt new file mode 100644 index 000000000..cabb59b17 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.231/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.231/templates/admission.yaml b/charts/speedscale/speedscale-operator/2.2.231/templates/admission.yaml new file mode 100644 index 000000000..9a2a943d3 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.231/templates/admission.yaml @@ -0,0 +1,199 @@ +{{- $cacrt := "" -}} +{{- $crt := "" -}} +{{- $key := "" -}} +{{- $s := (lookup "v1" "Secret" .Release.Namespace "speedscale-webhook-certs") -}} +{{- if $s -}} +{{- $cacrt = index $s.data "ca.crt" | default (index $s.data "tls.crt") | b64dec -}} +{{- $crt = index $s.data "tls.crt" | b64dec -}} +{{- $key = index $s.data "tls.key" | b64dec -}} +{{ else }} +{{- $altNames := list ( printf "speedscale-operator.%s" .Release.Namespace ) ( printf "speedscale-operator.%s.svc" .Release.Namespace ) -}} +{{- $ca := genCA "speedscale-operator" 3650 -}} +{{- $cert := genSignedCert "speedscale-operator" nil $altNames 3650 $ca -}} +{{- $cacrt = $ca.Cert -}} +{{- $crt = $cert.Cert -}} +{{- $key = $cert.Key -}} +{{- end -}} +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + creationTimestamp: null + name: speedscale-operator + annotations: + argocd.argoproj.io/hook: PreSync + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 4}} + {{- end }} +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + caBundle: {{ $cacrt | b64enc }} + service: + name: speedscale-operator + namespace: {{ .Release.Namespace }} + path: /mutate + failurePolicy: Ignore + name: sidecar.speedscale.com + namespaceSelector: + matchExpressions: + - key: kubernetes.io/metadata.name + operator: "NotIn" + values: + - kube-system + - kube-node-lease + {{- if .Values.namespaceSelector }} + - key: kubernetes.io/metadata.name + operator: "In" + values: + {{- range .Values.namespaceSelector }} + - {{ . | quote }} + {{- end }} + {{- end }} + reinvocationPolicy: IfNeeded + rules: + - apiGroups: + - apps + - batch + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + - DELETE + resources: + - deployments + - statefulsets + - daemonsets + - jobs + - replicasets + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + - DELETE + resources: + - pods + sideEffects: None + timeoutSeconds: 10 +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + creationTimestamp: null + name: speedscale-operator-replay + annotations: + argocd.argoproj.io/hook: PreSync + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 4}} + {{- end }} +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + caBundle: {{ $cacrt | b64enc }} + service: + name: speedscale-operator + namespace: {{ .Release.Namespace }} + path: /mutate-speedscale-com-v1-trafficreplay + failurePolicy: Fail + name: replay.speedscale.com + namespaceSelector: + matchExpressions: + - key: kubernetes.io/metadata.name + operator: "NotIn" + values: + - kube-system + - kube-node-lease + {{- if .Values.namespaceSelector }} + - key: kubernetes.io/metadata.name + operator: "In" + values: + {{- range .Values.namespaceSelector }} + - {{ . | quote }} + {{- end }} + {{- end }} + rules: + - apiGroups: + - speedscale.com + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - trafficreplays + sideEffects: None + timeoutSeconds: 10 +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + creationTimestamp: null + name: speedscale-operator-replay + annotations: + argocd.argoproj.io/hook: PreSync + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 4}} + {{- end }} +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + caBundle: {{ $cacrt | b64enc }} + service: + name: speedscale-operator + namespace: {{ .Release.Namespace }} + path: /validate-speedscale-com-v1-trafficreplay + failurePolicy: Fail + name: replay.speedscale.com + namespaceSelector: + matchExpressions: + - key: kubernetes.io/metadata.name + operator: "NotIn" + values: + - kube-system + - kube-node-lease + {{- if .Values.namespaceSelector }} + - key: kubernetes.io/metadata.name + operator: "In" + values: + {{- range .Values.namespaceSelector }} + - {{ . | quote }} + {{- end }} + {{- end }} + rules: + - apiGroups: + - speedscale.com + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + - DELETE + resources: + - trafficreplays + sideEffects: None + timeoutSeconds: 10 +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + helm.sh/hook: pre-install + helm.sh/hook-delete-policy: before-hook-creation + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 4}} + {{- end }} + creationTimestamp: null + name: speedscale-webhook-certs + namespace: {{ .Release.Namespace }} +type: kubernetes.io/tls +data: + ca.crt: {{ $cacrt | b64enc }} + tls.crt: {{ $crt | b64enc }} + tls.key: {{ $key | b64enc }} diff --git a/charts/speedscale/speedscale-operator/2.2.231/templates/configmap.yaml b/charts/speedscale/speedscale-operator/2.2.231/templates/configmap.yaml new file mode 100644 index 000000000..af735e288 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.231/templates/configmap.yaml @@ -0,0 +1,41 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: speedscale-operator + namespace: {{ .Release.Namespace }} + annotations: + argocd.argoproj.io/hook: PreSync + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 4}} + {{- end }} +data: + CLUSTER_NAME: {{ .Values.clusterName }} + IMAGE_PULL_POLICY: {{ .Values.image.pullPolicy }} + IMAGE_PULL_SECRETS: "" + IMAGE_REGISTRY: {{ .Values.image.registry }} + IMAGE_TAG: {{ .Values.image.tag }} + INSTANCE_ID: '{{- $cm := (lookup "v1" "ConfigMap" .Release.Namespace "speedscale-operator") -}}{{ if $cm }}{{ $cm.data.INSTANCE_ID }}{{ else }}{{ ( printf "%s-%s" .Values.clusterName uuidv4 ) }}{{ end }}' + LOG_LEVEL: {{ .Values.logLevel }} + SPEEDSCALE_DLP_CONFIG: {{ .Values.dlp.config }} + SPEEDSCALE_FILTER_RULE: {{ .Values.filterRule }} + TELEMETRY_INTERVAL: 1s + WITH_DLP: {{ .Values.dlp.enabled | quote }} + WITH_INSPECTOR: {{ .Values.dashboardAccess | quote }} + API_KEY_SECRET_NAME: {{ .Values.apiKeySecret | quote }} + DEPLOY_DEMO: {{ .Values.deployDemo | quote }} + GLOBAL_ANNOTATIONS: {{ .Values.globalAnnotations | toJson | quote }} + GLOBAL_LABELS: {{ .Values.globalLabels | toJson | quote }} + {{- if .Values.http_proxy }} + HTTP_PROXY: {{ .Values.http_proxy }} + {{- end }} + {{- if .Values.https_proxy }} + HTTPS_PROXY: {{ .Values.https_proxy }} + {{- end }} + {{- if .Values.no_proxy }} + NO_PROXY: {{ .Values.no_proxy }} + {{- end }} + PRIVILEGED_SIDECARS: {{ .Values.privilegedSidecars | quote }} + DISABLE_SMARTDNS: {{ .Values.disableSidecarSmartReverseDNS | quote }} + SIDECAR_CONFIG: {{ .Values.sidecar | toJson | quote }} + FORWARDER_CONFIG: {{ .Values.forwarder | toJson | quote }} diff --git a/charts/speedscale/speedscale-operator/2.2.231/templates/crds/trafficreplays.yaml b/charts/speedscale/speedscale-operator/2.2.231/templates/crds/trafficreplays.yaml new file mode 100644 index 000000000..9a85d5da4 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.231/templates/crds/trafficreplays.yaml @@ -0,0 +1,514 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + creationTimestamp: null + name: trafficreplays.speedscale.com +spec: + group: speedscale.com + names: + kind: TrafficReplay + listKind: TrafficReplayList + plural: trafficreplays + shortNames: + - replay + singular: trafficreplay + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.active + name: Active + type: boolean + - jsonPath: .spec.mode + name: Mode + type: string + - jsonPath: .status.conditions[-1:].message + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: TrafficReplay is the Schema for the trafficreplays API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: TrafficReplaySpec defines the desired state of TrafficReplay + properties: + buildTag: + description: |- + BuildTag links a unique tag, build hash, etc. to the generated + traffic replay report. That way you can connect the report results to the + version of the code that was tested. + type: string + cleanup: + description: |- + Cleanup is the name of cleanup mode used for this + TrafficReplay. + enum: + - inventory + - all + - none + type: string + collectLogs: + description: |- + CollectLogs enables or disables log collection from target + workload. Defaults to true. + DEPRECATED: use TestReport.ActualConfig.Cluster.CollectLogs + type: boolean + configChecksum: + description: |- + ConfigChecksum, managed my the operator, is the SHA1 checksum of the + configuration. + type: string + customURL: + description: CustomURL allows to specify custom URL to the SUT. + type: string + generatorLowData: + description: |- + GeneratorLowData forces the generator into a high + efficiency/low data output mode. This is ideal for high volume + performance tests. Defaults to false. + DEPRECATED + type: boolean + mode: + description: Mode is the name of replay mode used for this TrafficReplay. + enum: + - full-replay + - responder-only + - generator-only + type: string + needsReport: + description: Indicates whether a responder-only replay needs a report. + type: boolean + proxyMode: + description: |- + ProxyMode defines proxy operational mode used with injected sidecar. + DEPRECATED + type: string + responderLowData: + description: |- + ResponderLowData forces the responder into a high + efficiency/low data output mode. This is ideal for high volume + performance tests. Defaults to false. + DEPRECATED + type: boolean + secretRefs: + description: |- + SecretRefs hold the references to the secrets which contain + various secrets like (e.g. short-lived JWTs to be used by the generator + for authorization with HTTP calls). + items: + description: |- + LocalObjectReference contains enough information to locate the referenced + Kubernetes resource object. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + type: array + sidecar: + description: |- + Sidecar defines sidecar specific configuration. + DEPRECATED: use Workloads + properties: + inject: + description: 'DEPRECATED: do not use' + type: boolean + patch: + description: Patch is .yaml file patch for the Workload + format: byte + type: string + tls: + properties: + in: + description: In provides configuration for sidecar inbound + TLS. + properties: + private: + description: Private is the filename of the TLS inbound + private key. + type: string + public: + description: Public is the filename of the TLS inbound + public key. + type: string + secret: + description: Secret is a secret with the TLS keys to use + for inbound traffic. + type: string + type: object + mutual: + description: Mutual provides configuration for sidecar mutual + TLS. + properties: + private: + description: Private is the filename of the mutual TLS + private key. + type: string + public: + description: Public is the filename of the mutual TLS + public key. + type: string + secret: + description: Secret is a secret with the mutual TLS keys. + type: string + type: object + out: + description: |- + Out enables or disables TLS out on the + sidecar during replay. + type: boolean + type: object + type: object + snapshotID: + description: |- + SnapshotID is the id of the traffic snapshot for this + TrafficReplay. + type: string + testConfigID: + description: |- + TestConfigID is the id of the replay configuration to be used + by the generator and responder for the TrafficReplay. + type: string + timeout: + description: |- + Timeout is the time to wait for replay test to finish. Defaults + to value of the `TIMEOUT` setting of the operator. + type: string + ttlAfterReady: + description: |- + TTLAfterReady provides a TTL (time to live) mechanism to limit + the lifetime of TrafficReplay object that have finished the execution and + reached its final state (either complete or failed). + type: string + workloadRef: + description: |- + WorkloadRef is the reference to the target workload (SUT) for + TrafficReplay. The operations will be performed in the namespace of the + target object. + DEPRECATED: use Workloads + properties: + apiVersion: + description: API version of the referenced object. + type: string + kind: + description: Kind of the referenced object. Defaults to "Deployment". + type: string + name: + description: Name of the referenced object. + type: string + namespace: + description: Namespace of the referenced object. Defaults to the + TrafficReplay namespace. + type: string + required: + - name + type: object + workloads: + description: |- + Workloads define target workloads (SUT) for a TrafficReplay. Many + workloads may be provided, or none. Workloads may be modified and + restarted during replay to configure communication with a responder. + items: + description: |- + Workload represents a Kubernetes workload to be targeted during replay and + associated settings. + properties: + customURI: + description: CustomURI will be target of the traffic instead + of directly targeting workload + type: string + inTrafficKey: + description: 'DEPRECATED: use InTrafficKeys' + type: string + inTrafficKeys: + description: 'DEPRECATED: use Tests' + items: + type: string + type: array + mocks: + description: |- + Mocks are strings used to identify slices of outbound snapshot traffic to + mock for this workload and maps directly to a snapshot's `OutTraffic` + field. Snapshot egress traffic can be split across multiple slices where + each slice contains part of the traffic. A workload may specify multiple + keys and multiple workloads may specify the same key. + + + Only the traffic slices defined here will be mocked. A workload with no + keys defined will not mock any traffic. Pass '*' to mock all traffic. + + + Mock strings may only match part of the snapshot's `OutTraffic` key if the + string matches exactly one key. For example, the test string + `foo.example.com` would match the `OutTraffic` key of + my-service:foo.example.com:8080, as long as no other keys would match + `foo.example.com`. Multiple mocks must be specified for multiple keys + unless using '*'. + items: + type: string + type: array + outTrafficKeys: + description: 'DEPRECATED: use Mocks' + items: + type: string + type: array + ref: + description: |- + Ref is a reference to a cluster workload, like a deployment or a + statefulset. + properties: + apiVersion: + description: API version of the referenced object. + type: string + kind: + description: Kind of the referenced object. Defaults to + "Deployment". + type: string + name: + description: Name of the referenced object. + type: string + namespace: + description: Namespace of the referenced object. Defaults + to the TrafficReplay namespace. + type: string + required: + - name + type: object + routing: + description: Routing configures how workloads route egress traffic + to responders + enum: + - hostalias + - nat + type: string + sidecar: + description: |- + TODO: this is not implemented, come back and replace deprecated Sidecar with workload specific settings + Sidecar defines sidecar specific configuration. + properties: + inject: + description: 'DEPRECATED: do not use' + type: boolean + patch: + description: Patch is .yaml file patch for the Workload + format: byte + type: string + tls: + properties: + in: + description: In provides configuration for sidecar inbound + TLS. + properties: + private: + description: Private is the filename of the TLS + inbound private key. + type: string + public: + description: Public is the filename of the TLS inbound + public key. + type: string + secret: + description: Secret is a secret with the TLS keys + to use for inbound traffic. + type: string + type: object + mutual: + description: Mutual provides configuration for sidecar + mutual TLS. + properties: + private: + description: Private is the filename of the mutual + TLS private key. + type: string + public: + description: Public is the filename of the mutual + TLS public key. + type: string + secret: + description: Secret is a secret with the mutual + TLS keys. + type: string + type: object + out: + description: |- + Out enables or disables TLS out on the + sidecar during replay. + type: boolean + type: object + type: object + tests: + description: |- + Tests are strings used to identify slices of inbound snapshot traffic this + workload is targeting and maps directly to a snapshot's `InTraffic` field. + Snapshot ingress traffic can be split across multiple slices where each + slice contains part of the traffic. A key must only be specified once + across all workloads, but a workload may specify multiple keys. + + + 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.231/templates/deployments.yaml b/charts/speedscale/speedscale-operator/2.2.231/templates/deployments.yaml new file mode 100644 index 000000000..e5f329257 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.231/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.231/templates/hooks.yaml b/charts/speedscale/speedscale-operator/2.2.231/templates/hooks.yaml new file mode 100644 index 000000000..3e8231f19 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.231/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.231/templates/rbac.yaml b/charts/speedscale/speedscale-operator/2.2.231/templates/rbac.yaml new file mode 100644 index 000000000..e1ea42d99 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.231/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.231/templates/secrets.yaml b/charts/speedscale/speedscale-operator/2.2.231/templates/secrets.yaml new file mode 100644 index 000000000..1fb6999e4 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.231/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.231/templates/services.yaml b/charts/speedscale/speedscale-operator/2.2.231/templates/services.yaml new file mode 100644 index 000000000..f9da2c25c --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.231/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.231/templates/tls.yaml b/charts/speedscale/speedscale-operator/2.2.231/templates/tls.yaml new file mode 100644 index 000000000..4a2456288 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.231/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.231/values.yaml b/charts/speedscale/speedscale-operator/2.2.231/values.yaml new file mode 100644 index 000000000..6f229376e --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.231/values.yaml @@ -0,0 +1,133 @@ +# An API key is required to connect to the Speedscale cloud. +# If you need a key email support@speedscale.com. +apiKey: "" + +# A secret name can be referenced instead of the api key itself. +# The secret must be of the format: +# +# type: Opaque +# data: +# SPEEDSCALE_API_KEY: +# SPEEDSCALE_APP_URL: +apiKeySecret: "" + +# Speedscale domain to use. +appUrl: "app.speedscale.com" + +# The name of your cluster. +clusterName: "my-cluster" + +# Speedscale components image settings. +image: + registry: gcr.io/speedscale + tag: v2.2.231 + pullPolicy: Always + +# Log level for Speedscale components. +logLevel: "info" + +# Namespaces to be watched by Speedscale Operator as a list of names. +namespaceSelector: [] + +# Instructs operator to deploy resources necessary to interact with your cluster from the Speedscale dashboard. +dashboardAccess: true + +# Filter Rule to apply to the Speedscale Forwarder +filterRule: "standard" + +# Data Loss Prevention settings. +dlp: + # Instructs operator to enable data loss prevention features + enabled: false + + # Configuration for data loss prevention + config: "standard" + +# If the operator pod/webhooks need to be on the host network. +# This is only needed if the control plane cannot connect directly to a pod +# for eg. if Calico is used as EKS's default networking +# https://docs.tigera.io/calico/3.25/getting-started/kubernetes/managed-public-cloud/eks#install-eks-with-calico-networking +hostNetwork: false + +# A set of annotations to be applied to all Speedscale related deployments, +# services, jobs, pods, etc. +# +# Example: +# annotation.first: value +# annotation.second: value +globalAnnotations: {} + +# A set of labels to be applied to all Speedscale related deployments, +# services, jobs, pods, etc. +# +# Example: +# label1: value +# label2: value +globalLabels: {} + +# A full affinity object as detailed: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity +affinity: {} + +# The list of tolerations as detailed: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ +tolerations: [] + +# A nodeselector object as detailed: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes/ +nodeSelector: {} + +# Deploy a demo app at startup. Set this to an empty string to not deploy. +# Valid values: ["java", ""] +deployDemo: "java" + +# Proxy connection settings if required by your network. These translate to standard proxy environment +# variables HTTP_PROXY, HTTPS_PROXY, and NO_PROXY +http_proxy: "" +https_proxy: "" +no_proxy: "" + +# control if sidecar init containers should run with privileged set +privilegedSidecars: false + +# control if the sidecar should enable/disable use of the smart dns lookup feature (requires NET_ADMIN) +disableSidecarSmartReverseDNS: false + +# Operator settings. These limits are recommended unless you have a cluster +# with a very large number of workloads (for eg. 10k+ deployments, replicasets, etc.). +operator: + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + +# Default sidecar settings. Example: +# sidecar: +# resources: +# limits: +# cpu: 500m +# memory: 512Mi +# ephemeral-storage: 100Mi +# requests: +# cpu: 10m +# memory: 32Mi +# ephemeral-storage: 100Mi +# ignore_src_hosts: example.com, example.org +# ignore_src_ips: 8.8.8.8, 1.1.1.1 +# ignore_dst_hosts: example.com, example.org +# ignore_dst_ips: 8.8.8.8, 1.1.1.1 +# insert_init_first: false +# tls_out: false +# reinitialize_iptables: false +sidecar: {} + +# Forwarder settings +# forwarder: +# resources: +# limits: +# cpu: 500m +# memory: 500M +# requests: +# cpu: 300m +# memory: 250M +forwarder: {} diff --git a/charts/traefik/traefik/30.0.2/.helmignore b/charts/traefik/traefik/30.0.2/.helmignore new file mode 100644 index 000000000..9c42ddd90 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/.helmignore @@ -0,0 +1,2 @@ +tests/ +crds/kustomization.yaml diff --git a/charts/traefik/traefik/30.0.2/Changelog.md b/charts/traefik/traefik/30.0.2/Changelog.md new file mode 100644 index 000000000..4adf40076 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/Changelog.md @@ -0,0 +1,9135 @@ +# Change Log + +## 30.0.2 ![AppVersion: v3.1.0](https://img.shields.io/static/v1?label=AppVersion&message=v3.1.0&color=success&logo=) ![Kubernetes: >=1.22.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.22.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2024-07-30 + +* fix(Traefik Hub): missing RBACs for Traefik Hub +* chore(release): 🚀 publish v30.0.2 + +## 30.0.1 ![AppVersion: v3.1.0](https://img.shields.io/static/v1?label=AppVersion&message=v3.1.0&color=success&logo=) ![Kubernetes: >=1.22.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.22.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2024-07-29 + +* fix(Traefik Hub): support new RBACs for upcoming traefik hub release +* fix(Traefik Hub): RBACs missing with API Gateway +* feat: :release: v30.0.1 + +## 30.0.0 ![AppVersion: v3.1.0](https://img.shields.io/static/v1?label=AppVersion&message=v3.1.0&color=success&logo=) ![Kubernetes: >=1.22.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.22.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2024-07-24 + +* fix: 🐛 ingressroute default name +* fix: namespaced RBACs hub api gateway +* fix: can't set gateway name +* fix(Gateway API): provide expected roles when using namespaced RBAC +* fix(Gateway API)!: revamp Gateway implementation +* feat: ✨ display release name and image full path in installation notes +* feat: use single ingressRoute template +* feat: handle log filePath and noColor +* chore(release): 🚀 publish v30.0.0 +* chore(deps): update traefik docker tag to v3.1.0 + +**Upgrade Notes** + +There is a breaking upgrade on how to configure Gateway with _values_. +This release supports Traefik Proxy v3.0 **and** v3.1. + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index c8bfd5b..83b6d98 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -134,14 +134,36 @@ gateway: + enabled: true + # -- Set a custom name to gateway + name: +- # -- Routes are restricted to namespace of the gateway [by default](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.FromNamespaces) +- namespacePolicy: +- # -- See [GatewayTLSConfig](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io%2fv1.GatewayTLSConfig) +- certificateRefs: + # -- By default, Gateway is created in the same `Namespace` than Traefik. + namespace: + # -- Additional gateway annotations (e.g. for cert-manager.io/issuer) + annotations: ++ # -- Define listeners ++ listeners: ++ web: ++ # -- Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. ++ # The port must match a port declared in ports section. ++ port: 8000 ++ # -- Optional hostname. See [Hostname](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.Hostname) ++ hostname: ++ # Specify expected protocol on this listener. See [ProtocolType](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.ProtocolType) ++ protocol: HTTP ++ # -- Routes are restricted to namespace of the gateway [by default](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.FromNamespaces ++ namespacePolicy: ++ websecure: ++ # -- Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. ++ # The port must match a port declared in ports section. ++ port: 8443 ++ # -- Optional hostname. See [Hostname](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.Hostname) ++ hostname: ++ # Specify expected protocol on this listener See [ProtocolType](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.ProtocolType) ++ protocol: HTTPS ++ # -- Routes are restricted to namespace of the gateway [by default](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.FromNamespaces) ++ namespacePolicy: ++ # -- Add certificates for TLS or HTTPS protocols. See [GatewayTLSConfig](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io%2fv1.GatewayTLSConfig) ++ certificateRefs: ++ # -- TLS behavior for the TLS session initiated by the client. See [TLSModeType](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.TLSModeType). ++ mode: + + gatewayClass: + # -- When providers.kubernetesGateway.enabled and gateway.enabled, deploy a default gatewayClass +@@ -161,6 +183,10 @@ ingressRoute: + labels: {} + # -- The router match rule used for the dashboard ingressRoute + matchRule: PathPrefix(`/dashboard`) || PathPrefix(`/api`) ++ # -- The internal service used for the dashboard ingressRoute ++ services: ++ - name: api@internal ++ kind: TraefikService + # -- Specify the allowed entrypoints to use for the dashboard ingress route, (e.g. traefik, web, websecure). + # By default, it's using traefik entrypoint, which is not exposed. + # /!\ Do not expose your dashboard without any protection over the internet /!\ +@@ -178,6 +204,10 @@ ingressRoute: + labels: {} + # -- The router match rule used for the healthcheck ingressRoute + matchRule: PathPrefix(`/ping`) ++ # -- The internal service used for the healthcheck ingressRoute ++ services: ++ - name: ping@internal ++ kind: TraefikService + # -- Specify the allowed entrypoints to use for the healthcheck ingress route, (e.g. traefik, web, websecure). + # By default, it's using traefik entrypoint, which is not exposed. + entryPoints: ["traefik"] +@@ -307,9 +337,12 @@ logs: + # -- Set [logs format](https://doc.traefik.io/traefik/observability/logs/#format) + # @default common + format: +- # By default, the level is set to ERROR. ++ # By default, the level is set to INFO. + # -- Alternative logging levels are DEBUG, PANIC, FATAL, ERROR, WARN, and INFO. + level: INFO ++ # ++ # filePath: "/var/log/traefik/traefik.log ++ # noColor: true + access: + # -- To enable access logs + enabled: false +``` + + +## 29.0.1 ![AppVersion: v3.0.4](https://img.shields.io/static/v1?label=AppVersion&message=v3.0.4&color=success&logo=) ![Kubernetes: >=1.22.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.22.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2024-07-09 + +* fix: semverCompare failing on some legitimate tags +* fix: RBACs for hub and disabled namespaced RBACs +* chore(release): 🚀 publish v29.0.1 +* chore(deps): update jnorwood/helm-docs docker tag to v1.14.0 + +## 29.0.0 ![AppVersion: v3.0.4](https://img.shields.io/static/v1?label=AppVersion&message=v3.0.4&color=success&logo=) ![Kubernetes: >=1.22.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.22.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Upgrade Notes** + +This is a major breaking upgrade. [Migration guide](https://doc.traefik.io/traefik/v3.1/migration/v3/#v30-to-v31) from v3.0 to v3.1rc has been applied on this chart. + +This release supports both Traefik Proxy v3.0.x and v3.1rc. + +It comes with those breaking changes: + +- Far better support on Gateway API v1.1: Gateway, GatewayClass, CRDs & RBAC (#1107) +- Many changes on CRDs & RBAC (#1072 & #1108) +- Refactor on Prometheus Operator support. Values has changed (#1114) +- Dashboard `IngressRoute` is now disabled by default (#1111) + +CRDs needs to be upgraded: `kubectl apply --server-side --force-conflicts -k https://github.com/traefik/traefik-helm-chart/traefik/crds/` + +**Release date:** 2024-07-05 + +* fix: 🐛 improve error message on additional service without ports +* fix: allow multiples values in the `secretResourceNames` slice +* fix(rbac)!: nodes API permissions for Traefik v3.1+ +* fix(dashboard): Only set ingressClass annotation when kubernetesCRD provider is listening for it +* fix!: prometheus operator settings +* feat: ✨ update CRDs & RBAC for Traefik Proxy +* feat: ✨ migrate to endpointslices rbac +* feat: allow to set hostAliases for traefik pod +* feat(providers): add nativeLBByDefault support +* feat(providers)!: improve kubernetesGateway and Gateway API support +* feat(dashboard)!: dashboard `IngressRoute` should be disabled by default +* docs: fix typos and broken link +* chore: update CRDs to v1.5.0 +* chore: update CRDs to v1.4.0 +* chore(release): publish v29.0.0 +* chore(deps): update traefik docker tag to v3.0.4 +* chore(deps): update traefik docker tag to v3.0.3 + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index e440dcf..c8bfd5b 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -8,7 +8,7 @@ image: + # -- Traefik image repository + repository: traefik + # -- defaults to appVersion +- tag: "" ++ tag: + # -- Traefik image pull policy + pullPolicy: IfNotPresent + +@@ -81,19 +81,12 @@ deployment: + shareProcessNamespace: false + # -- Custom pod DNS policy. Apply if `hostNetwork: true` + # dnsPolicy: ClusterFirstWithHostNet ++ # -- Custom pod [DNS config](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#poddnsconfig-v1-core) + dnsConfig: {} +- # nameservers: +- # - 192.0.2.1 # this is an example +- # searches: +- # - ns1.svc.cluster-domain.example +- # - my.dns.search.suffix +- # options: +- # - name: ndots +- # value: "2" +- # - name: edns0 +- # -- Additional imagePullSecrets ++ # -- Custom [host aliases](https://kubernetes.io/docs/tasks/network/customize-hosts-file-for-pods/) ++ hostAliases: [] ++ # -- Pull secret for fetching traefik container image + imagePullSecrets: [] +- # - name: myRegistryKeySecretName + # -- Pod lifecycle actions + lifecycle: {} + # preStop: +@@ -135,24 +128,33 @@ experimental: + kubernetesGateway: + # -- Enable traefik experimental GatewayClass CRD + enabled: false +- ## Routes are restricted to namespace of the gateway by default. +- ## https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.FromNamespaces +- # namespacePolicy: All +- # certificate: +- # group: "core" +- # kind: "Secret" +- # name: "mysecret" +- # -- By default, Gateway would be created to the Namespace you are deploying Traefik to. +- # You may create that Gateway in another namespace, setting its name below: +- # namespace: default +- # Additional gateway annotations (e.g. for cert-manager.io/issuer) +- # annotations: +- # cert-manager.io/issuer: letsencrypt ++ ++gateway: ++ # -- When providers.kubernetesGateway.enabled, deploy a default gateway ++ enabled: true ++ # -- Set a custom name to gateway ++ name: ++ # -- Routes are restricted to namespace of the gateway [by default](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.FromNamespaces) ++ namespacePolicy: ++ # -- See [GatewayTLSConfig](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io%2fv1.GatewayTLSConfig) ++ certificateRefs: ++ # -- By default, Gateway is created in the same `Namespace` than Traefik. ++ namespace: ++ # -- Additional gateway annotations (e.g. for cert-manager.io/issuer) ++ annotations: ++ ++gatewayClass: ++ # -- When providers.kubernetesGateway.enabled and gateway.enabled, deploy a default gatewayClass ++ enabled: true ++ # -- Set a custom name to GatewayClass ++ name: ++ # -- Additional gatewayClass labels (e.g. for filtering gateway objects by custom labels) ++ labels: + + ingressRoute: + dashboard: + # -- Create an IngressRoute for the dashboard +- enabled: true ++ enabled: false + # -- Additional ingressRoute annotations (e.g. for kubernetes.io/ingress.class) + annotations: {} + # -- Additional ingressRoute labels (e.g. for filtering IngressRoute by custom labels) +@@ -227,11 +229,13 @@ providers: + allowExternalNameServices: false + # -- Allows to return 503 when there is no endpoints available + allowEmptyServices: false +- # ingressClass: traefik-internal ++ # -- When the parameter is set, only resources containing an annotation with the same value are processed. Otherwise, resources missing the annotation, having an empty value, or the value traefik are processed. It will also set required annotation on Dashboard and Healthcheck IngressRoute when enabled. ++ ingressClass: + # labelSelector: environment=production,method=traefik + # -- Array of namespaces to watch. If left empty, Traefik watches all namespaces. + namespaces: [] +- # - "default" ++ # -- Defines whether to use Native Kubernetes load-balancing mode by default. ++ nativeLBByDefault: + + kubernetesIngress: + # -- Load Kubernetes Ingress provider +@@ -240,7 +244,8 @@ providers: + allowExternalNameServices: false + # -- Allows to return 503 when there is no endpoints available + allowEmptyServices: false +- # ingressClass: traefik-internal ++ # -- When ingressClass is set, only Ingresses containing an annotation with the same value are processed. Otherwise, Ingresses missing the annotation, having an empty value, or the value traefik are processed. ++ ingressClass: + # labelSelector: environment=production,method=traefik + # -- Array of namespaces to watch. If left empty, Traefik watches all namespaces. + namespaces: [] +@@ -254,6 +259,19 @@ providers: + # Published Kubernetes Service to copy status from. Format: namespace/servicename + # By default this Traefik service + # pathOverride: "" ++ # -- Defines whether to use Native Kubernetes load-balancing mode by default. ++ nativeLBByDefault: ++ ++ kubernetesGateway: ++ # -- Enable Traefik Gateway provider for Gateway API ++ enabled: false ++ # -- Toggles support for the Experimental Channel resources (Gateway API release channels documentation). ++ # This option currently enables support for TCPRoute and TLSRoute. ++ experimentalChannel: false ++ # -- Array of namespaces to watch. If left empty, Traefik watches all namespaces. ++ namespaces: [] ++ # -- A label selector can be defined to filter on specific GatewayClass objects only. ++ labelselector: + + file: + # -- Create a file provider +@@ -341,6 +359,34 @@ metrics: + ## When manualRouting is true, it disables the default internal router in + ## order to allow creating a custom router for prometheus@internal service. + # manualRouting: true ++ service: ++ # -- Create a dedicated metrics service to use with ServiceMonitor ++ enabled: ++ labels: ++ annotations: ++ # -- When set to true, it won't check if Prometheus Operator CRDs are deployed ++ disableAPICheck: ++ serviceMonitor: ++ # -- Enable optional CR for Prometheus Operator. See EXAMPLES.md for more details. ++ enabled: false ++ metricRelabelings: ++ relabelings: ++ jobLabel: ++ interval: ++ honorLabels: ++ scrapeTimeout: ++ honorTimestamps: ++ enableHttp2: ++ followRedirects: ++ additionalLabels: ++ namespace: ++ namespaceSelector: ++ prometheusRule: ++ # -- Enable optional CR for Prometheus Operator. See EXAMPLES.md for more details. ++ enabled: false ++ additionalLabels: ++ namespace: ++ + # datadog: + # ## Address instructs exporter to send metrics to datadog-agent at this address. + # address: "127.0.0.1:8125" +@@ -436,55 +482,6 @@ metrics: + # -- When set to true, the TLS connection accepts any certificate presented by the server regardless of the hostnames it covers. + insecureSkipVerify: + +- ## -- enable optional CRDs for Prometheus Operator +- ## +- ## Create a dedicated metrics service for use with ServiceMonitor +- # service: +- # enabled: false +- # labels: {} +- # annotations: {} +- ## When set to true, it won't check if Prometheus Operator CRDs are deployed +- # disableAPICheck: false +- # serviceMonitor: +- # metricRelabelings: [] +- # - sourceLabels: [__name__] +- # separator: ; +- # regex: ^fluentd_output_status_buffer_(oldest|newest)_.+ +- # replacement: $1 +- # action: drop +- # relabelings: [] +- # - sourceLabels: [__meta_kubernetes_pod_node_name] +- # separator: ; +- # regex: ^(.*)$ +- # targetLabel: nodename +- # replacement: $1 +- # action: replace +- # jobLabel: traefik +- # interval: 30s +- # honorLabels: true +- # # (Optional) +- # # scrapeTimeout: 5s +- # # honorTimestamps: true +- # # enableHttp2: true +- # # followRedirects: true +- # # additionalLabels: +- # # foo: bar +- # # namespace: "another-namespace" +- # # namespaceSelector: {} +- # prometheusRule: +- # additionalLabels: {} +- # namespace: "another-namespace" +- # rules: +- # - alert: TraefikDown +- # expr: up{job="traefik"} == 0 +- # for: 5m +- # labels: +- # context: traefik +- # severity: warning +- # annotations: +- # summary: "Traefik Down" +- # description: "{{ $labels.pod }} on {{ $labels.nodename }} is down" +- + ## Tracing + # -- https://doc.traefik.io/traefik/observability/tracing/overview/ + tracing: +``` + +## 28.3.0 ![AppVersion: v3.0.2](https://img.shields.io/static/v1?label=AppVersion&message=v3.0.2&color=success&logo=) ![Kubernetes: >=1.22.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.22.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2024-06-14 + +* fix: 🐛 namespaced rbac when kubernetesIngress provider is disabled +* fix: 🐛 add divisor: '1' to GOMAXPROCS and GOMEMLIMIT +* fix(security): 🐛 🔒️ mount service account token on pod level +* fix(Traefik Hub): remove obsolete CRD +* fix(Traefik Hub): remove namespace in mutating webhook +* feat: allow setting permanent on redirectTo +* chore(release): publish v28.3.0 +* chore(deps): update traefik docker tag to v3.0.2 + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index c558c78..e440dcf 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -602,6 +602,7 @@ ports: + # port: websecure + # (Optional) + # priority: 10 ++ # permanent: true + # + # -- Trust forwarded headers information (X-Forwarded-*). + # forwardedHeaders: +``` + +## 28.2.0 ![AppVersion: v3.0.1](https://img.shields.io/static/v1?label=AppVersion&message=v3.0.1&color=success&logo=) ![Kubernetes: >=1.22.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.22.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2024-05-28 + +* fix(IngressClass): provides annotation on IngressRoutes when it's enabled +* feat: ✨ simplify values and provide more examples +* feat: add deletecollection right on secrets +* chore(release): 🚀 publish v28.2.0 +* chore(deps): update traefik docker tag to v3.0.1 + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 2fd9282..c558c78 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -1,4 +1,7 @@ + # Default values for Traefik ++# This is a YAML-formatted file. ++# Declare variables to be passed into templates ++ + image: + # -- Traefik image host registry + registry: docker.io +@@ -12,9 +15,6 @@ image: + # -- Add additional label to all resources + commonLabels: {} + +-# +-# Configure the deployment +-# + deployment: + # -- Enable deployment + enabled: true +@@ -74,10 +74,6 @@ deployment: + # - name: volume-permissions + # image: busybox:latest + # command: ["sh", "-c", "touch /data/acme.json; chmod -v 600 /data/acme.json"] +- # securityContext: +- # runAsNonRoot: true +- # runAsGroup: 65532 +- # runAsUser: 65532 + # volumeMounts: + # - name: data + # mountPath: /data +@@ -112,13 +108,11 @@ deployment: + # -- Set a runtimeClassName on pod + runtimeClassName: + +-# -- Pod disruption budget ++# -- [Pod Disruption Budget](https://kubernetes.io/docs/reference/kubernetes-api/policy-resources/pod-disruption-budget-v1/) + podDisruptionBudget: +- enabled: false +- # maxUnavailable: 1 +- # maxUnavailable: 33% +- # minAvailable: 0 +- # minAvailable: 25% ++ enabled: ++ maxUnavailable: ++ minAvailable: + + # -- Create a default IngressClass for Traefik + ingressClass: +@@ -155,7 +149,6 @@ experimental: + # annotations: + # cert-manager.io/issuer: letsencrypt + +-## Create an IngressRoute for the dashboard + ingressRoute: + dashboard: + # -- Create an IngressRoute for the dashboard +@@ -221,15 +214,7 @@ livenessProbe: + # -- The number of seconds to wait for a probe response before considering it as failed. + timeoutSeconds: 2 + +-# -- Define Startup Probe for container: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-startup-probes +-# eg. +-# `startupProbe: +-# exec: +-# command: +-# - mycommand +-# - foo +-# initialDelaySeconds: 5 +-# periodSeconds: 5` ++# -- Define [Startup Probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-startup-probes) + startupProbe: + + providers: +@@ -276,18 +261,8 @@ providers: + # -- Allows Traefik to automatically watch for file changes + watch: true + # -- File content (YAML format, go template supported) (see https://doc.traefik.io/traefik/providers/file/) +- content: "" +- # http: +- # routers: +- # router0: +- # entryPoints: +- # - web +- # middlewares: +- # - my-basic-auth +- # service: service-foo +- # rule: Path(`/foo`) ++ content: + +-# + # -- Add volumes to the traefik pod. The volume name will be passed to tpl. + # This can be used to mount a cert pair or a configmap that holds a config.toml file. + # After the volume has been mounted, add the configs into traefik by using the `additionalArguments` list below, eg: +@@ -311,26 +286,21 @@ additionalVolumeMounts: [] + + logs: + general: +- # -- By default, the logs use a text format (common), but you can +- # also ask for the json format in the format option +- # format: json ++ # -- Set [logs format](https://doc.traefik.io/traefik/observability/logs/#format) ++ # @default common ++ format: + # By default, the level is set to ERROR. + # -- Alternative logging levels are DEBUG, PANIC, FATAL, ERROR, WARN, and INFO. + level: INFO + access: + # -- To enable access logs + enabled: false +- ## By default, logs are written using the Common Log Format (CLF) on stdout. +- ## To write logs in JSON, use json in the format option. +- ## If the given format is unsupported, the default (CLF) is used instead. +- # format: json ++ # -- Set [access log format](https://doc.traefik.io/traefik/observability/access-logs/#format) ++ format: + # filePath: "/var/log/traefik/access.log +- ## To write the logs in an asynchronous fashion, specify a bufferingSize option. +- ## This option represents the number of log lines Traefik will keep in memory before writing +- ## them to the selected output. In some cases, this option can greatly help performances. +- # bufferingSize: 100 +- ## Filtering +- # -- https://docs.traefik.io/observability/access-logs/#filtering ++ # -- Set [bufferingSize](https://doc.traefik.io/traefik/observability/access-logs/#bufferingsize) ++ bufferingSize: ++ # -- Set [filtering](https://docs.traefik.io/observability/access-logs/#filtering) + filters: {} + # statuscodes: "200,300-302" + # retryattempts: true +@@ -345,15 +315,11 @@ logs: + names: {} + ## Examples: + # ClientUsername: drop ++ # -- [Limit logged fields or headers](https://doc.traefik.io/traefik/observability/access-logs/#limiting-the-fieldsincluding-headers) + headers: + # -- Available modes: keep, drop, redact. + defaultmode: drop +- # -- Names of the headers to limit. + names: {} +- ## Examples: +- # User-Agent: redact +- # Authorization: drop +- # Content-Type: keep + + metrics: + ## -- Enable metrics for internal resources. Default: false +@@ -567,16 +533,15 @@ globalArguments: + - "--global.checknewversion" + - "--global.sendanonymoususage" + +-# +-# Configure Traefik static configuration + # -- Additional arguments to be passed at Traefik's binary +-# All available options available on https://docs.traefik.io/reference/static-configuration/cli/ +-## Use curly braces to pass values: `helm install --set="additionalArguments={--providers.kubernetesingress.ingressclass=traefik-internal,--log.level=DEBUG}"` ++# See [CLI Reference](https://docs.traefik.io/reference/static-configuration/cli/) ++# Use curly braces to pass values: `helm install --set="additionalArguments={--providers.kubernetesingress.ingressclass=traefik-internal,--log.level=DEBUG}"` + additionalArguments: [] + # - "--providers.kubernetesingress.ingressclass=traefik-internal" + # - "--log.level=DEBUG" + + # -- Environment variables to be passed to Traefik's binary ++# @default -- See _values.yaml_ + env: + - name: POD_NAME + valueFrom: +@@ -586,25 +551,9 @@ env: + valueFrom: + fieldRef: + fieldPath: metadata.namespace +-# - name: SOME_VAR +-# value: some-var-value +-# - name: SOME_VAR_FROM_CONFIG_MAP +-# valueFrom: +-# configMapRef: +-# name: configmap-name +-# key: config-key +-# - name: SOME_SECRET +-# valueFrom: +-# secretKeyRef: +-# name: secret-name +-# key: secret-key + + # -- Environment variables to be passed to Traefik's binary from configMaps or secrets + envFrom: [] +-# - configMapRef: +-# name: config-map-name +-# - secretRef: +-# name: secret-name + + ports: + traefik: +@@ -766,28 +715,12 @@ ports: + # -- The port protocol (TCP/UDP) + protocol: TCP + +-# -- TLS Options are created as TLSOption CRDs +-# https://doc.traefik.io/traefik/https/tls/#tls-options ++# -- TLS Options are created as [TLSOption CRDs](https://doc.traefik.io/traefik/https/tls/#tls-options) + # When using `labelSelector`, you'll need to set labels on tlsOption accordingly. +-# Example: +-# tlsOptions: +-# default: +-# labels: {} +-# sniStrict: true +-# custom-options: +-# labels: {} +-# curvePreferences: +-# - CurveP521 +-# - CurveP384 ++# See EXAMPLE.md for details. + tlsOptions: {} + +-# -- TLS Store are created as TLSStore CRDs. This is useful if you want to set a default certificate +-# https://doc.traefik.io/traefik/https/tls/#default-certificate +-# Example: +-# tlsStore: +-# default: +-# defaultCertificate: +-# secretName: tls-cert ++# -- TLS Store are created as [TLSStore CRDs](https://doc.traefik.io/traefik/https/tls/#default-certificate). This is useful if you want to set a default certificate. See EXAMPLE.md for details. + tlsStore: {} + + service: +@@ -839,29 +772,8 @@ service: + + autoscaling: + # -- Create HorizontalPodAutoscaler object. ++ # See EXAMPLES.md for more details. + enabled: false +-# minReplicas: 1 +-# maxReplicas: 10 +-# metrics: +-# - type: Resource +-# resource: +-# name: cpu +-# target: +-# type: Utilization +-# averageUtilization: 60 +-# - type: Resource +-# resource: +-# name: memory +-# target: +-# type: Utilization +-# averageUtilization: 60 +-# behavior: +-# scaleDown: +-# stabilizationWindowSeconds: 300 +-# policies: +-# - type: Pods +-# value: 1 +-# periodSeconds: 60 + + persistence: + # -- Enable persistence using Persistent Volume Claims +@@ -879,27 +791,10 @@ persistence: + # -- Only mount a subpath of the Volume into the pod + # subPath: "" + +-# -- Certificates resolvers configuration ++# -- Certificates resolvers configuration. ++# Ref: https://doc.traefik.io/traefik/https/acme/#certificate-resolvers ++# See EXAMPLES.md for more details. + certResolvers: {} +-# letsencrypt: +-# # for challenge options cf. https://doc.traefik.io/traefik/https/acme/ +-# email: email@example.com +-# dnsChallenge: +-# # also add the provider's required configuration under env +-# # or expand then from secrets/configmaps with envfrom +-# # cf. https://doc.traefik.io/traefik/https/acme/#providers +-# provider: digitalocean +-# # add futher options for the dns challenge as needed +-# # cf. https://doc.traefik.io/traefik/https/acme/#dnschallenge +-# delayBeforeCheck: 30 +-# resolvers: +-# - 1.1.1.1 +-# - 8.8.8.8 +-# tlsChallenge: true +-# httpChallenge: +-# entryPoint: "web" +-# # It has to match the path with a persistent volume +-# storage: /data/acme.json + + # -- If hostNetwork is true, runs traefik in the host network namespace + # To prevent unschedulabel pods due to port collisions, if hostNetwork=true +@@ -933,14 +828,8 @@ serviceAccount: + # -- Additional serviceAccount annotations (e.g. for oidc authentication) + serviceAccountAnnotations: {} + +-# -- The resources parameter defines CPU and memory requirements and limits for Traefik's containers. ++# -- [Resources](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for `traefik` container. + resources: {} +-# requests: +-# cpu: "100m" +-# memory: "50Mi" +-# limits: +-# cpu: "300m" +-# memory: "150Mi" + + # -- This example pod anti-affinity forces the scheduler to put traefik pods + # -- on nodes where no other traefik pods are scheduled. +@@ -970,30 +859,22 @@ topologySpreadConstraints: [] + # topologyKey: kubernetes.io/hostname + # whenUnsatisfiable: DoNotSchedule + +-# -- Pods can have priority. +-# -- Priority indicates the importance of a Pod relative to other Pods. ++# -- [Pod Priority and Preemption](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/) + priorityClassName: "" + +-# -- Set the container security context +-# -- To run the container with ports below 1024 this will need to be adjusted to run as root ++# -- [SecurityContext](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#security-context-1) ++# @default -- See _values.yaml_ + securityContext: ++ allowPrivilegeEscalation: false + capabilities: + drop: [ALL] + readOnlyRootFilesystem: true +- allowPrivilegeEscalation: false + ++# -- [Pod Security Context](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#security-context) ++# @default -- See _values.yaml_ + podSecurityContext: +- # /!\ When setting fsGroup, Kubernetes will recursively change ownership and +- # permissions for the contents of each volume to match the fsGroup. This can +- # be an issue when storing sensitive content like TLS Certificates /!\ +- # fsGroup: 65532 +- # -- Specifies the policy for changing ownership and permissions of volume contents to match the fsGroup. +- fsGroupChangePolicy: "OnRootMismatch" +- # -- The ID of the group for all containers in the pod to run as. + runAsGroup: 65532 +- # -- Specifies whether the containers should run as a non-root user. + runAsNonRoot: true +- # -- The ID of the user for all containers in the pod to run as. + runAsUser: 65532 + + # +@@ -1003,16 +884,16 @@ podSecurityContext: + # See #595 for more details and traefik/tests/values/extra.yaml for example. + extraObjects: [] + +-# This will override the default Release Namespace for Helm. ++# -- This field override the default Release Namespace for Helm. + # It will not affect optional CRDs such as `ServiceMonitor` and `PrometheusRules` +-# namespaceOverride: traefik +-# +-## -- This will override the default app.kubernetes.io/instance label for all Objects. +-# instanceLabelOverride: traefik ++namespaceOverride: ++ ++## -- This field override the default app.kubernetes.io/instance label for all Objects. ++instanceLabelOverride: + +-# -- Traefik Hub configuration. See https://doc.traefik.io/traefik-hub/ ++# Traefik Hub configuration. See https://doc.traefik.io/traefik-hub/ + hub: +- # Name of Secret with key 'token' set to a valid license token. ++ # -- Name of `Secret` with key 'token' set to a valid license token. + # It enables API Gateway. + token: + apimanagement: +``` + +## 28.1.0 ![AppVersion: v3.0.0](https://img.shields.io/static/v1?label=AppVersion&message=v3.0.0&color=success&logo=) ![Kubernetes: >=1.22.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.22.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +* fix(Traefik Hub): do not deploy mutating webhook when enabling only API Gateway +* feat(Traefik Hub): use Traefik Proxy otlp config +* chore: 🔧 update Traefik Hub CRD to v1.3.3 + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 70297f6..2fd9282 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -1010,3 +1010,49 @@ + ## -- This will override the default app.kubernetes.io/instance label for all Objects. + # instanceLabelOverride: traefik + ++# -- Traefik Hub configuration. See https://doc.traefik.io/traefik-hub/ ++hub: ++ # Name of Secret with key 'token' set to a valid license token. ++ # It enables API Gateway. ++ token: ++ apimanagement: ++ # -- Set to true in order to enable API Management. Requires a valid license token. ++ enabled: ++ admission: ++ # -- WebHook admission server listen address. Default: "0.0.0.0:9943". ++ listenAddr: ++ # -- Certificate of the WebHook admission server. Default: "hub-agent-cert". ++ secretName: ++ ++ ratelimit: ++ redis: ++ # -- Enable Redis Cluster. Default: true. ++ cluster: ++ # -- Database used to store information. Default: "0". ++ database: ++ # -- Endpoints of the Redis instances to connect to. Default: "". ++ endpoints: ++ # -- The username to use when connecting to Redis endpoints. Default: "". ++ username: ++ # -- The password to use when connecting to Redis endpoints. Default: "". ++ password: ++ sentinel: ++ # -- Name of the set of main nodes to use for main selection. Required when using Sentinel. Default: "". ++ masterset: ++ # -- Username to use for sentinel authentication (can be different from endpoint username). Default: "". ++ username: ++ # -- Password to use for sentinel authentication (can be different from endpoint password). Default: "". ++ password: ++ # -- Timeout applied on connection with redis. Default: "0s". ++ timeout: ++ tls: ++ # -- Path to the certificate authority used for the secured connection. ++ ca: ++ # -- Path to the public certificate used for the secure connection. ++ cert: ++ # -- Path to the private key used for the secure connection. ++ key: ++ # -- When insecureSkipVerify is set to true, the TLS connection accepts any certificate presented by the server. Default: false. ++ insecureSkipVerify: ++ # Enable export of errors logs to the platform. Default: true. ++ sendlogs: +``` + +## 28.1.0-beta.3 ![AppVersion: v3.0.0](https://img.shields.io/static/v1?label=AppVersion&message=v3.0.0&color=success&logo=) ![Kubernetes: >=1.22.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.22.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2024-05-03 + +* chore: 🔧 update Traefik Hub CRD to v1.3.2 +* chore(release): 🚀 publish v28.1.0-beta.3 + +## 28.1.0-beta.2 ![AppVersion: v3.0.0](https://img.shields.io/static/v1?label=AppVersion&message=v3.0.0&color=success&logo=) ![Kubernetes: >=1.22.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.22.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2024-05-02 + +* fix: 🐛 refine Traefik Hub support +* chore(release): 🚀 publish v28.1.0-beta.2 + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index ce0a7a3..70297f6 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -1015,13 +1015,15 @@ hub: + # Name of Secret with key 'token' set to a valid license token. + # It enables API Gateway. + token: +- admission: +- # -- WebHook admission server listen address. Default: "0.0.0.0:9943". +- listenAddr: +- # -- Certificate of the WebHook admission server. Default: "hub-agent-cert". +- secretName: +- # -- Set to true in order to enable API Management. Requires a valid license token. + apimanagement: ++ # -- Set to true in order to enable API Management. Requires a valid license token. ++ enabled: ++ admission: ++ # -- WebHook admission server listen address. Default: "0.0.0.0:9943". ++ listenAddr: ++ # -- Certificate of the WebHook admission server. Default: "hub-agent-cert". ++ secretName: ++ + metrics: + opentelemetry: + # -- Set to true to enable OpenTelemetry metrics exporter of Traefik Hub. +``` + +## 28.1.0-beta.1 ![AppVersion: v3.0.0](https://img.shields.io/static/v1?label=AppVersion&message=v3.0.0&color=success&logo=) ![Kubernetes: >=1.22.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.22.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2024-04-30 + +* feat: :rocket: add initial support for Traefik Hub Api Gateway +* chore(release): 🚀 publish v28.1.0-beta.1 + +## 28.0.0 ![AppVersion: v3.0.0](https://img.shields.io/static/v1?label=AppVersion&message=v3.0.0&color=success&logo=) ![Kubernetes: >=1.22.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.22.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2024-04-30 + +* style: 🎨 consistent capitalization on `--entryPoints` CLI flag +* fix: 🐛 only expose http3 port on service when TCP variant is exposed +* fix: 🐛 logs filters on status codes +* feat: ✨ add support of `experimental-v3.0` unstable version +* feat: ability to override liveness and readiness probe paths +* feat(ports): add transport options +* chore(release): publish v28.0.0 + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index c0d72d8..2bff10d 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -38,6 +38,12 @@ deployment: + ## Override the liveness/readiness scheme. Useful for getting ping to + ## respond on websecure entryPoint. + # healthchecksScheme: HTTPS ++ ## Override the readiness path. ++ ## Default: /ping ++ # readinessPath: /ping ++ # Override the liveness path. ++ # Default: /ping ++ # livenessPath: /ping + # -- Additional deployment annotations (e.g. for jaeger-operator sidecar injection) + annotations: {} + # -- Additional deployment labels (e.g. for filtering deployment by custom labels) +@@ -648,15 +654,28 @@ ports: + # (Optional) + # priority: 10 + # +- # Trust forwarded headers information (X-Forwarded-*). ++ # -- Trust forwarded headers information (X-Forwarded-*). + # forwardedHeaders: + # trustedIPs: [] + # insecure: false + # +- # Enable the Proxy Protocol header parsing for the entry point ++ # -- Enable the Proxy Protocol header parsing for the entry point + # proxyProtocol: + # trustedIPs: [] + # insecure: false ++ # ++ # -- Set transport settings for the entrypoint; see also ++ # https://doc.traefik.io/traefik/routing/entrypoints/#transport ++ transport: ++ respondingTimeouts: ++ readTimeout: ++ writeTimeout: ++ idleTimeout: ++ lifeCycle: ++ requestAcceptGraceTimeout: ++ graceTimeOut: ++ keepAliveMaxRequests: ++ keepAliveMaxTime: + websecure: + ## -- Enable this entrypoint as a default entrypoint. When a service doesn't explicitly set an entrypoint it will only use this entrypoint. + # asDefault: true +@@ -684,16 +703,29 @@ ports: + enabled: false + # advertisedPort: 4443 + # +- ## -- Trust forwarded headers information (X-Forwarded-*). ++ # -- Trust forwarded headers information (X-Forwarded-*). + # forwardedHeaders: + # trustedIPs: [] + # insecure: false + # +- ## -- Enable the Proxy Protocol header parsing for the entry point ++ # -- Enable the Proxy Protocol header parsing for the entry point + # proxyProtocol: + # trustedIPs: [] + # insecure: false + # ++ # -- Set transport settings for the entrypoint; see also ++ # https://doc.traefik.io/traefik/routing/entrypoints/#transport ++ transport: ++ respondingTimeouts: ++ readTimeout: ++ writeTimeout: ++ idleTimeout: ++ lifeCycle: ++ requestAcceptGraceTimeout: ++ graceTimeOut: ++ keepAliveMaxRequests: ++ keepAliveMaxTime: ++ # + ## Set TLS at the entrypoint + ## https://doc.traefik.io/traefik/routing/entrypoints/#tls + tls: +``` + +## 28.0.0-rc1 ![AppVersion: v3.0.0-rc5](https://img.shields.io/static/v1?label=AppVersion&message=v3.0.0-rc5&color=success&logo=) ![Kubernetes: >=1.16.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.16.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2024-04-17 + +**Upgrade Notes** + +This is a major breaking upgrade. [Migration guide](https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/) have been applied on the chart. + +It needs a Kubernetes v1.22 or higher. +All CRDs using _API Group_ `traefik.containo.us` are not supported anymore in Traefik Proxy v3 + +CRDs needs to be upgraded: `kubectl apply --server-side --force-conflicts -k https://github.com/traefik/traefik-helm-chart/traefik/crds/` + +After upgrade, CRDs with _API Group_ `traefik.containo.us` can be removed: + +```shell +kubectl delete crds \ + ingressroutes.traefik.containo.us \ + ingressroutetcps.traefik.containo.us \ + ingressrouteudps.traefik.containo.us \ + middlewares.traefik.containo.us \ + middlewaretcps.traefik.containo.us \ + serverstransports.traefik.containo.us \ + tlsoptions.traefik.containo.us \ + tlsstores.traefik.containo.us \ + traefikservices.traefik.containo.us +``` + +**Changes** + +* feat(podtemplate): set GOMEMLIMIT, GOMAXPROCS when limits are defined +* feat: ✨ fail gracefully when required port number is not set +* feat!: :boom: initial support of Traefik Proxy v3 +* docs: 📚️ improve EXAMPLES on acme resolver +* chore(release): 🚀 publish v28 rc1 + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index cd9fb6e..c0d72d8 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -120,12 +120,13 @@ ingressClass: + isDefaultClass: true + # name: my-custom-class + ++core: ++ # -- Can be used to use globally v2 router syntax ++ # See https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#new-v3-syntax-notable-changes ++ defaultRuleSyntax: ++ + # Traefik experimental features + experimental: +- # This value is no longer used, set the image.tag to a semver higher than 3.0, e.g. "v3.0.0-beta3" +- # v3: +- # -- Enable traefik version 3 +- + # -- Enable traefik experimental plugins + plugins: {} + # demo: +@@ -309,7 +310,7 @@ logs: + # format: json + # By default, the level is set to ERROR. + # -- Alternative logging levels are DEBUG, PANIC, FATAL, ERROR, WARN, and INFO. +- level: ERROR ++ level: INFO + access: + # -- To enable access logs + enabled: false +@@ -328,6 +329,8 @@ logs: + # statuscodes: "200,300-302" + # retryattempts: true + # minduration: 10ms ++ # -- Enables accessLogs for internal resources. Default: false. ++ addInternals: + fields: + general: + # -- Available modes: keep, drop, redact. +@@ -347,6 +350,9 @@ logs: + # Content-Type: keep + + metrics: ++ ## -- Enable metrics for internal resources. Default: false ++ addInternals: ++ + ## -- Prometheus is enabled by default. + ## -- It can be disabled by setting "prometheus: null" + prometheus: +@@ -376,31 +382,6 @@ metrics: + # # addRoutersLabels: true + # ## Enable metrics on services. Default=true + # # addServicesLabels: false +- # influxdb: +- # ## Address instructs exporter to send metrics to influxdb at this address. +- # address: localhost:8089 +- # ## InfluxDB's address protocol (udp or http). Default="udp" +- # protocol: udp +- # ## InfluxDB database used when protocol is http. Default="" +- # # database: "" +- # ## InfluxDB retention policy used when protocol is http. Default="" +- # # retentionPolicy: "" +- # ## InfluxDB username (only with http). Default="" +- # # username: "" +- # ## InfluxDB password (only with http). Default="" +- # # password: "" +- # ## The interval used by the exporter to push metrics to influxdb. Default=10s +- # # pushInterval: 30s +- # ## Additional labels (influxdb tags) on all metrics. +- # # additionalLabels: +- # # env: production +- # # foo: bar +- # ## Enable metrics on entry points. Default=true +- # # addEntryPointsLabels: false +- # ## Enable metrics on routers. Default=false +- # # addRoutersLabels: true +- # ## Enable metrics on services. Default=true +- # # addServicesLabels: false + # influxdb2: + # ## Address instructs exporter to send metrics to influxdb v2 at this address. + # address: localhost:8086 +@@ -435,43 +416,53 @@ metrics: + # # addRoutersLabels: true + # ## Enable metrics on services. Default=true + # # addServicesLabels: false +- # openTelemetry: +- # ## Address of the OpenTelemetry Collector to send metrics to. +- # address: "localhost:4318" +- # ## Enable metrics on entry points. +- # addEntryPointsLabels: true +- # ## Enable metrics on routers. +- # addRoutersLabels: true +- # ## Enable metrics on services. +- # addServicesLabels: true +- # ## Explicit boundaries for Histogram data points. +- # explicitBoundaries: +- # - "0.1" +- # - "0.3" +- # - "1.2" +- # - "5.0" +- # ## Additional headers sent with metrics by the reporter to the OpenTelemetry Collector. +- # headers: +- # foo: bar +- # test: test +- # ## Allows reporter to send metrics to the OpenTelemetry Collector without using a secured protocol. +- # insecure: true +- # ## Interval at which metrics are sent to the OpenTelemetry Collector. +- # pushInterval: 10s +- # ## Allows to override the default URL path used for sending metrics. This option has no effect when using gRPC transport. +- # path: /foo/v1/traces +- # ## Defines the TLS configuration used by the reporter to send metrics to the OpenTelemetry Collector. +- # tls: +- # ## The path to the certificate authority, it defaults to the system bundle. +- # ca: path/to/ca.crt +- # ## The path to the public certificate. When using this option, setting the key option is required. +- # cert: path/to/foo.cert +- # ## The path to the private key. When using this option, setting the cert option is required. +- # key: path/to/key.key +- # ## If set to true, the TLS connection accepts any certificate presented by the server regardless of the hostnames it covers. +- # insecureSkipVerify: true +- # ## This instructs the reporter to send metrics to the OpenTelemetry Collector using gRPC. +- # grpc: true ++ otlp: ++ # -- Set to true in order to enable the OpenTelemetry metrics ++ enabled: false ++ # -- Enable metrics on entry points. Default: true ++ addEntryPointsLabels: ++ # -- Enable metrics on routers. Default: false ++ addRoutersLabels: ++ # -- Enable metrics on services. Default: true ++ addServicesLabels: ++ # -- Explicit boundaries for Histogram data points. Default: [.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10] ++ explicitBoundaries: ++ # -- Interval at which metrics are sent to the OpenTelemetry Collector. Default: 10s ++ pushInterval: ++ http: ++ # -- Set to true in order to send metrics to the OpenTelemetry Collector using HTTP. ++ enabled: false ++ # -- Format: ://:. Default: http://localhost:4318/v1/metrics ++ endpoint: ++ # -- Additional headers sent with metrics by the reporter to the OpenTelemetry Collector. ++ headers: ++ ## Defines the TLS configuration used by the reporter to send metrics to the OpenTelemetry Collector. ++ tls: ++ # -- The path to the certificate authority, it defaults to the system bundle. ++ ca: ++ # -- The path to the public certificate. When using this option, setting the key option is required. ++ cert: ++ # -- The path to the private key. When using this option, setting the cert option is required. ++ key: ++ # -- When set to true, the TLS connection accepts any certificate presented by the server regardless of the hostnames it covers. ++ insecureSkipVerify: ++ grpc: ++ # -- Set to true in order to send metrics to the OpenTelemetry Collector using gRPC ++ enabled: false ++ # -- Format: ://:. Default: http://localhost:4318/v1/metrics ++ endpoint: ++ # -- Allows reporter to send metrics to the OpenTelemetry Collector without using a secured protocol. ++ insecure: ++ ## Defines the TLS configuration used by the reporter to send metrics to the OpenTelemetry Collector. ++ tls: ++ # -- The path to the certificate authority, it defaults to the system bundle. ++ ca: ++ # -- The path to the public certificate. When using this option, setting the key option is required. ++ cert: ++ # -- The path to the private key. When using this option, setting the cert option is required. ++ key: ++ # -- When set to true, the TLS connection accepts any certificate presented by the server regardless of the hostnames it covers. ++ insecureSkipVerify: + + ## -- enable optional CRDs for Prometheus Operator + ## +@@ -524,51 +515,46 @@ metrics: + + ## Tracing + # -- https://doc.traefik.io/traefik/observability/tracing/overview/ +-tracing: {} +-# openTelemetry: # traefik v3+ only +-# grpc: true +-# insecure: true +-# address: localhost:4317 +-# instana: +-# localAgentHost: 127.0.0.1 +-# localAgentPort: 42699 +-# logLevel: info +-# enableAutoProfile: true +-# datadog: +-# localAgentHostPort: 127.0.0.1:8126 +-# debug: false +-# globalTag: "" +-# prioritySampling: false +-# jaeger: +-# samplingServerURL: http://localhost:5778/sampling +-# samplingType: const +-# samplingParam: 1.0 +-# localAgentHostPort: 127.0.0.1:6831 +-# gen128Bit: false +-# propagation: jaeger +-# traceContextHeaderName: uber-trace-id +-# disableAttemptReconnecting: true +-# collector: +-# endpoint: "" +-# user: "" +-# password: "" +-# zipkin: +-# httpEndpoint: http://localhost:9411/api/v2/spans +-# sameSpan: false +-# id128Bit: true +-# sampleRate: 1.0 +-# haystack: +-# localAgentHost: 127.0.0.1 +-# localAgentPort: 35000 +-# globalTag: "" +-# traceIDHeaderName: "" +-# parentIDHeaderName: "" +-# spanIDHeaderName: "" +-# baggagePrefixHeaderName: "" +-# elastic: +-# serverURL: http://localhost:8200 +-# secretToken: "" +-# serviceEnvironment: "" ++tracing: ++ # -- Enables tracing for internal resources. Default: false. ++ addInternals: ++ otlp: ++ # -- See https://doc.traefik.io/traefik/v3.0/observability/tracing/opentelemetry/ ++ enabled: false ++ http: ++ # -- Set to true in order to send metrics to the OpenTelemetry Collector using HTTP. ++ enabled: false ++ # -- Format: ://:. Default: http://localhost:4318/v1/metrics ++ endpoint: ++ # -- Additional headers sent with metrics by the reporter to the OpenTelemetry Collector. ++ headers: ++ ## Defines the TLS configuration used by the reporter to send metrics to the OpenTelemetry Collector. ++ tls: ++ # -- The path to the certificate authority, it defaults to the system bundle. ++ ca: ++ # -- The path to the public certificate. When using this option, setting the key option is required. ++ cert: ++ # -- The path to the private key. When using this option, setting the cert option is required. ++ key: ++ # -- When set to true, the TLS connection accepts any certificate presented by the server regardless of the hostnames it covers. ++ insecureSkipVerify: ++ grpc: ++ # -- Set to true in order to send metrics to the OpenTelemetry Collector using gRPC ++ enabled: false ++ # -- Format: ://:. Default: http://localhost:4318/v1/metrics ++ endpoint: ++ # -- Allows reporter to send metrics to the OpenTelemetry Collector without using a secured protocol. ++ insecure: ++ ## Defines the TLS configuration used by the reporter to send metrics to the OpenTelemetry Collector. ++ tls: ++ # -- The path to the certificate authority, it defaults to the system bundle. ++ ca: ++ # -- The path to the public certificate. When using this option, setting the key option is required. ++ cert: ++ # -- The path to the private key. When using this option, setting the cert option is required. ++ key: ++ # -- When set to true, the TLS connection accepts any certificate presented by the server regardless of the hostnames it covers. ++ insecureSkipVerify: + + # -- Global command arguments to be passed to all traefik's pods + globalArguments: +@@ -756,7 +742,6 @@ ports: + # default: + # labels: {} + # sniStrict: true +-# preferServerCipherSuites: true + # custom-options: + # labels: {} + # curvePreferences: +``` + +## 27.0.0 ![AppVersion: v2.11.0](https://img.shields.io/static/v1?label=AppVersion&message=v2.11.0&color=success&logo=) ![Kubernetes: >=1.16.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.16.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2024-04-02 + +**Upgrade notes** + +Custom services and port exposure have been redesigned, requiring the following changes: +- if you were overriding port exposure behavior using the `expose` or `exposeInternal` flags, you should replace them with a service name to boolean mapping, i.e. replace this: + +```yaml +ports: + web: + expose: false + exposeInternal: true +``` + +with this: + +```yaml +ports: + web: + expose: + default: false + internal: true +``` + +- if you were previously using the `service.internal` value, you should migrate the values to the `service.additionalServices.internal` value instead; this should yield the same results, but make sure to carefully check for any changes! + +**Changes** + +* fix: remove null annotations on dashboard `IngressRoute` +* fix(rbac): do not create clusterrole for namespace deployment on Traefik v3 +* feat: restrict access to secrets +* feat!: :boom: refactor custom services and port exposure +* chore(release): 🚀 publish v27.0.0 + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index dbd078f..363871d 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -250,6 +250,9 @@ providers: + # -- Array of namespaces to watch. If left empty, Traefik watches all namespaces. + namespaces: [] + # - "default" ++ # Disable cluster IngressClass Lookup - Requires Traefik V3. ++ # When combined with rbac.namespaced: true, ClusterRole will not be created and ingresses must use kubernetes.io/ingress.class annotation instead of spec.ingressClassName. ++ disableIngressClassLookup: false + # IP used for Kubernetes Ingress endpoints + publishedService: + enabled: false +@@ -626,22 +629,20 @@ ports: + # -- You SHOULD NOT expose the traefik port on production deployments. + # If you want to access it from outside your cluster, + # use `kubectl port-forward` or create a secure ingress +- expose: false ++ expose: ++ default: false + # -- The exposed port for this service + exposedPort: 9000 + # -- The port protocol (TCP/UDP) + protocol: TCP +- # -- Defines whether the port is exposed on the internal service; +- # note that ports exposed on the default service are exposed on the internal +- # service by default as well. +- exposeInternal: false + web: + ## -- Enable this entrypoint as a default entrypoint. When a service doesn't explicitly set an entrypoint it will only use this entrypoint. + # asDefault: true + port: 8000 + # hostPort: 8000 + # containerPort: 8000 +- expose: true ++ expose: ++ default: true + exposedPort: 80 + ## -- Different target traefik port on the cluster, useful for IP type LB + # targetPort: 80 +@@ -650,10 +651,6 @@ ports: + # -- Use nodeport if set. This is useful if you have configured Traefik in a + # LoadBalancer. + # nodePort: 32080 +- # -- Defines whether the port is exposed on the internal service; +- # note that ports exposed on the default service are exposed on the internal +- # service by default as well. +- exposeInternal: false + # Port Redirections + # Added in 2.2, you can make permanent redirects via entrypoints. + # https://docs.traefik.io/routing/entrypoints/#redirection +@@ -677,17 +674,14 @@ ports: + port: 8443 + # hostPort: 8443 + # containerPort: 8443 +- expose: true ++ expose: ++ default: true + exposedPort: 443 + ## -- Different target traefik port on the cluster, useful for IP type LB + # targetPort: 80 + ## -- The port protocol (TCP/UDP) + protocol: TCP + # nodePort: 32443 +- # -- Defines whether the port is exposed on the internal service; +- # note that ports exposed on the default service are exposed on the internal +- # service by default as well. +- exposeInternal: false + ## -- Specify an application protocol. This may be used as a hint for a Layer 7 load balancer. + # appProtocol: https + # +@@ -744,15 +738,12 @@ ports: + # -- You may not want to expose the metrics port on production deployments. + # If you want to access it from outside your cluster, + # use `kubectl port-forward` or create a secure ingress +- expose: false ++ expose: ++ default: false + # -- The exposed port for this service + exposedPort: 9100 + # -- The port protocol (TCP/UDP) + protocol: TCP +- # -- Defines whether the port is exposed on the internal service; +- # note that ports exposed on the default service are exposed on the internal +- # service by default as well. +- exposeInternal: false + + # -- TLS Options are created as TLSOption CRDs + # https://doc.traefik.io/traefik/https/tls/#tls-options +@@ -814,6 +805,7 @@ service: + # - IPv4 + # - IPv6 + ## ++ additionalServices: {} + ## -- An additional and optional internal Service. + ## Same parameters as external Service + # internal: +@@ -899,11 +891,14 @@ hostNetwork: false + rbac: + enabled: true + # If set to false, installs ClusterRole and ClusterRoleBinding so Traefik can be used across namespaces. +- # If set to true, installs Role and RoleBinding. Providers will only watch target namespace. ++ # If set to true, installs Role and RoleBinding instead of ClusterRole/ClusterRoleBinding. Providers will only watch target namespace. ++ # When combined with providers.kubernetesIngress.disableIngressClassLookup: true and Traefik V3, ClusterRole to watch IngressClass is also disabled. + namespaced: false + # Enable user-facing roles + # https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles + # aggregateTo: [ "admin" ] ++ # List of Kubernetes secrets that are accessible for Traefik. If empty, then access is granted to every secret. ++ secretResourceNames: [] + + # -- Enable to create a PodSecurityPolicy and assign it to the Service Account via RoleBinding or ClusterRoleBinding + podSecurityPolicy: +``` + +## 26.1.0 ![AppVersion: v2.11.0](https://img.shields.io/static/v1?label=AppVersion&message=v2.11.0&color=success&logo=) ![Kubernetes: >=1.16.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.16.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2024-02-19 + +* fix: 🐛 set runtimeClassName at pod level +* fix: 🐛 missing quote on experimental plugin args +* fix: update traefik v3 serverstransporttcps CRD +* feat: set runtimeClassName on pod spec +* feat: create v1 Gateway and GatewayClass Version for Traefik v3 +* feat: allow exposure of ports on internal service only +* doc: fix invalid suggestion on TLSOption (#996) +* chore: 🔧 update maintainers +* chore: 🔧 promote jnoordsij to Traefik Helm Chart maintainer +* chore(release): 🚀 publish v26.1.0 +* chore(deps): update traefik docker tag to v2.11.0 +* chore(deps): update traefik docker tag to v2.10.7 +* chore(crds): update definitions for traefik v2.11 + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index f9dac91..dbd078f 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -100,6 +100,8 @@ deployment: + # port: 9000 + # host: localhost + # scheme: HTTP ++ # -- Set a runtimeClassName on pod ++ runtimeClassName: + + # -- Pod disruption budget + podDisruptionBudget: +@@ -629,6 +631,10 @@ ports: + exposedPort: 9000 + # -- The port protocol (TCP/UDP) + protocol: TCP ++ # -- Defines whether the port is exposed on the internal service; ++ # note that ports exposed on the default service are exposed on the internal ++ # service by default as well. ++ exposeInternal: false + web: + ## -- Enable this entrypoint as a default entrypoint. When a service doesn't explicitly set an entrypoint it will only use this entrypoint. + # asDefault: true +@@ -644,6 +650,10 @@ ports: + # -- Use nodeport if set. This is useful if you have configured Traefik in a + # LoadBalancer. + # nodePort: 32080 ++ # -- Defines whether the port is exposed on the internal service; ++ # note that ports exposed on the default service are exposed on the internal ++ # service by default as well. ++ exposeInternal: false + # Port Redirections + # Added in 2.2, you can make permanent redirects via entrypoints. + # https://docs.traefik.io/routing/entrypoints/#redirection +@@ -674,6 +684,10 @@ ports: + ## -- The port protocol (TCP/UDP) + protocol: TCP + # nodePort: 32443 ++ # -- Defines whether the port is exposed on the internal service; ++ # note that ports exposed on the default service are exposed on the internal ++ # service by default as well. ++ exposeInternal: false + ## -- Specify an application protocol. This may be used as a hint for a Layer 7 load balancer. + # appProtocol: https + # +@@ -735,6 +749,10 @@ ports: + exposedPort: 9100 + # -- The port protocol (TCP/UDP) + protocol: TCP ++ # -- Defines whether the port is exposed on the internal service; ++ # note that ports exposed on the default service are exposed on the internal ++ # service by default as well. ++ exposeInternal: false + + # -- TLS Options are created as TLSOption CRDs + # https://doc.traefik.io/traefik/https/tls/#tls-options +@@ -745,7 +763,7 @@ ports: + # labels: {} + # sniStrict: true + # preferServerCipherSuites: true +-# customOptions: ++# custom-options: + # labels: {} + # curvePreferences: + # - CurveP521 +@@ -796,7 +814,7 @@ service: + # - IPv4 + # - IPv6 + ## +- ## -- An additionnal and optional internal Service. ++ ## -- An additional and optional internal Service. + ## Same parameters as external Service + # internal: + # type: ClusterIP +``` + +## 26.0.0 ![AppVersion: v2.10.6](https://img.shields.io/static/v1?label=AppVersion&message=v2.10.6&color=success&logo=) ![Kubernetes: >=1.16.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.16.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2023-12-05 + +* fix: 🐛 improve confusing suggested value on openTelemetry.grpc +* fix: 🐛 declare http3 udp port, with or without hostport +* feat: 💥 deployment.podannotations support interpolation with tpl +* feat: allow update of namespace policy for websecure listener +* feat: allow defining startupProbe +* feat: add file provider +* feat: :boom: unify plugin import between traefik and this chart +* chore(release): 🚀 publish v26 +* chore(deps): update traefik docker tag to v2.10.6 +* Release namespace for Prometheus Operator resources + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 71e377e..f9dac91 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -40,6 +40,7 @@ deployment: + # -- Additional deployment labels (e.g. for filtering deployment by custom labels) + labels: {} + # -- Additional pod annotations (e.g. for mesh injection or prometheus scraping) ++ # It supports templating. One can set it with values like traefik/name: '{{ template "traefik.name" . }}' + podAnnotations: {} + # -- Additional Pod labels (e.g. for filtering Pod by custom labels) + podLabels: {} +@@ -119,10 +120,12 @@ experimental: + # This value is no longer used, set the image.tag to a semver higher than 3.0, e.g. "v3.0.0-beta3" + # v3: + # -- Enable traefik version 3 +- # enabled: false +- plugins: +- # -- Enable traefik experimental plugins +- enabled: false ++ ++ # -- Enable traefik experimental plugins ++ plugins: {} ++ # demo: ++ # moduleName: github.com/traefik/plugindemo ++ # version: v0.2.1 + kubernetesGateway: + # -- Enable traefik experimental GatewayClass CRD + enabled: false +@@ -206,6 +209,17 @@ livenessProbe: + # -- The number of seconds to wait for a probe response before considering it as failed. + timeoutSeconds: 2 + ++# -- Define Startup Probe for container: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-startup-probes ++# eg. ++# `startupProbe: ++# exec: ++# command: ++# - mycommand ++# - foo ++# initialDelaySeconds: 5 ++# periodSeconds: 5` ++startupProbe: ++ + providers: + kubernetesCRD: + # -- Load Kubernetes IngressRoute provider +@@ -241,6 +255,23 @@ providers: + # By default this Traefik service + # pathOverride: "" + ++ file: ++ # -- Create a file provider ++ enabled: false ++ # -- Allows Traefik to automatically watch for file changes ++ watch: true ++ # -- File content (YAML format, go template supported) (see https://doc.traefik.io/traefik/providers/file/) ++ content: "" ++ # http: ++ # routers: ++ # router0: ++ # entryPoints: ++ # - web ++ # middlewares: ++ # - my-basic-auth ++ # service: service-foo ++ # rule: Path(`/foo`) ++ + # + # -- Add volumes to the traefik pod. The volume name will be passed to tpl. + # This can be used to mount a cert pair or a configmap that holds a config.toml file. +@@ -487,7 +518,7 @@ metrics: + # -- https://doc.traefik.io/traefik/observability/tracing/overview/ + tracing: {} + # openTelemetry: # traefik v3+ only +-# grpc: {} ++# grpc: true + # insecure: true + # address: localhost:4317 + # instana: +``` + +## 25.0.0 ![AppVersion: v2.10.5](https://img.shields.io/static/v1?label=AppVersion&message=v2.10.5&color=success&logo=) ![Kubernetes: >=1.16.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.16.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2023-10-23 + +* revert: "fix: 🐛 remove old CRDs using traefik.containo.us" +* fix: 🐛 remove old CRDs using traefik.containo.us +* fix: disable ClusterRole and ClusterRoleBinding when not needed +* fix: detect correctly v3 version when using sha in `image.tag` +* fix: allow updateStrategy.rollingUpdate.maxUnavailable to be passed in as an int or string +* fix: add missing separator in crds +* fix: add Prometheus scraping annotations only if serviceMonitor not created +* feat: ✨ add healthcheck ingressRoute +* feat: :boom: support http redirections and http challenges with cert-manager +* feat: :boom: rework and allow update of namespace policy for Gateway +* docs: Fix typo in the default values file +* chore: remove label whitespace at TLSOption +* chore(release): publish v25.0.0 +* chore(deps): update traefik docker tag to v2.10.5 +* chore(deps): update docker.io/helmunittest/helm-unittest docker tag to v3.12.3 +* chore(ci): 🔧 👷 add e2e test when releasing + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index aeec85c..71e377e 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -45,60 +45,60 @@ deployment: + podLabels: {} + # -- Additional containers (e.g. for metric offloading sidecars) + additionalContainers: [] +- # https://docs.datadoghq.com/developers/dogstatsd/unix_socket/?tab=host +- # - name: socat-proxy +- # image: alpine/socat:1.0.5 +- # args: ["-s", "-u", "udp-recv:8125", "unix-sendto:/socket/socket"] +- # volumeMounts: +- # - name: dsdsocket +- # mountPath: /socket ++ # https://docs.datadoghq.com/developers/dogstatsd/unix_socket/?tab=host ++ # - name: socat-proxy ++ # image: alpine/socat:1.0.5 ++ # args: ["-s", "-u", "udp-recv:8125", "unix-sendto:/socket/socket"] ++ # volumeMounts: ++ # - name: dsdsocket ++ # mountPath: /socket + # -- Additional volumes available for use with initContainers and additionalContainers + additionalVolumes: [] +- # - name: dsdsocket +- # hostPath: +- # path: /var/run/statsd-exporter ++ # - name: dsdsocket ++ # hostPath: ++ # path: /var/run/statsd-exporter + # -- Additional initContainers (e.g. for setting file permission as shown below) + initContainers: [] +- # The "volume-permissions" init container is required if you run into permission issues. +- # Related issue: https://github.com/traefik/traefik-helm-chart/issues/396 +- # - name: volume-permissions +- # image: busybox:latest +- # command: ["sh", "-c", "touch /data/acme.json; chmod -v 600 /data/acme.json"] +- # securityContext: +- # runAsNonRoot: true +- # runAsGroup: 65532 +- # runAsUser: 65532 +- # volumeMounts: +- # - name: data +- # mountPath: /data ++ # The "volume-permissions" init container is required if you run into permission issues. ++ # Related issue: https://github.com/traefik/traefik-helm-chart/issues/396 ++ # - name: volume-permissions ++ # image: busybox:latest ++ # command: ["sh", "-c", "touch /data/acme.json; chmod -v 600 /data/acme.json"] ++ # securityContext: ++ # runAsNonRoot: true ++ # runAsGroup: 65532 ++ # runAsUser: 65532 ++ # volumeMounts: ++ # - name: data ++ # mountPath: /data + # -- Use process namespace sharing + shareProcessNamespace: false + # -- Custom pod DNS policy. Apply if `hostNetwork: true` + # dnsPolicy: ClusterFirstWithHostNet + dnsConfig: {} +- # nameservers: +- # - 192.0.2.1 # this is an example +- # searches: +- # - ns1.svc.cluster-domain.example +- # - my.dns.search.suffix +- # options: +- # - name: ndots +- # value: "2" +- # - name: edns0 ++ # nameservers: ++ # - 192.0.2.1 # this is an example ++ # searches: ++ # - ns1.svc.cluster-domain.example ++ # - my.dns.search.suffix ++ # options: ++ # - name: ndots ++ # value: "2" ++ # - name: edns0 + # -- Additional imagePullSecrets + imagePullSecrets: [] +- # - name: myRegistryKeySecretName ++ # - name: myRegistryKeySecretName + # -- Pod lifecycle actions + lifecycle: {} +- # preStop: +- # exec: +- # command: ["/bin/sh", "-c", "sleep 40"] +- # postStart: +- # httpGet: +- # path: /ping +- # port: 9000 +- # host: localhost +- # scheme: HTTP ++ # preStop: ++ # exec: ++ # command: ["/bin/sh", "-c", "sleep 40"] ++ # postStart: ++ # httpGet: ++ # path: /ping ++ # port: 9000 ++ # host: localhost ++ # scheme: HTTP + + # -- Pod disruption budget + podDisruptionBudget: +@@ -116,9 +116,9 @@ ingressClass: + + # Traefik experimental features + experimental: +- #This value is no longer used, set the image.tag to a semver higher than 3.0, e.g. "v3.0.0-beta3" +- #v3: +- # -- Enable traefik version 3 ++ # This value is no longer used, set the image.tag to a semver higher than 3.0, e.g. "v3.0.0-beta3" ++ # v3: ++ # -- Enable traefik version 3 + # enabled: false + plugins: + # -- Enable traefik experimental plugins +@@ -126,9 +126,9 @@ experimental: + kubernetesGateway: + # -- Enable traefik experimental GatewayClass CRD + enabled: false +- gateway: +- # -- Enable traefik regular kubernetes gateway +- enabled: true ++ ## Routes are restricted to namespace of the gateway by default. ++ ## https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.FromNamespaces ++ # namespacePolicy: All + # certificate: + # group: "core" + # kind: "Secret" +@@ -159,6 +159,22 @@ ingressRoute: + middlewares: [] + # -- TLS options (e.g. secret containing certificate) + tls: {} ++ healthcheck: ++ # -- Create an IngressRoute for the healthcheck probe ++ enabled: false ++ # -- Additional ingressRoute annotations (e.g. for kubernetes.io/ingress.class) ++ annotations: {} ++ # -- Additional ingressRoute labels (e.g. for filtering IngressRoute by custom labels) ++ labels: {} ++ # -- The router match rule used for the healthcheck ingressRoute ++ matchRule: PathPrefix(`/ping`) ++ # -- Specify the allowed entrypoints to use for the healthcheck ingress route, (e.g. traefik, web, websecure). ++ # By default, it's using traefik entrypoint, which is not exposed. ++ entryPoints: ["traefik"] ++ # -- Additional ingressRoute middlewares (e.g. for authentication) ++ middlewares: [] ++ # -- TLS options (e.g. secret containing certificate) ++ tls: {} + + updateStrategy: + # -- Customize updateStrategy: RollingUpdate or OnDelete +@@ -204,10 +220,10 @@ providers: + # labelSelector: environment=production,method=traefik + # -- Array of namespaces to watch. If left empty, Traefik watches all namespaces. + namespaces: [] +- # - "default" ++ # - "default" + + kubernetesIngress: +- # -- Load Kubernetes IngressRoute provider ++ # -- Load Kubernetes Ingress provider + enabled: true + # -- Allows to reference ExternalName services in Ingress + allowExternalNameServices: false +@@ -217,7 +233,7 @@ providers: + # labelSelector: environment=production,method=traefik + # -- Array of namespaces to watch. If left empty, Traefik watches all namespaces. + namespaces: [] +- # - "default" ++ # - "default" + # IP used for Kubernetes Ingress endpoints + publishedService: + enabled: false +@@ -243,9 +259,9 @@ volumes: [] + + # -- Additional volumeMounts to add to the Traefik container + additionalVolumeMounts: [] +- # -- For instance when using a logshipper for access logs +- # - name: traefik-logs +- # mountPath: /var/log/traefik ++# -- For instance when using a logshipper for access logs ++# - name: traefik-logs ++# mountPath: /var/log/traefik + + logs: + general: +@@ -270,26 +286,26 @@ logs: + ## Filtering + # -- https://docs.traefik.io/observability/access-logs/#filtering + filters: {} +- # statuscodes: "200,300-302" +- # retryattempts: true +- # minduration: 10ms ++ # statuscodes: "200,300-302" ++ # retryattempts: true ++ # minduration: 10ms + fields: + general: + # -- Available modes: keep, drop, redact. + defaultmode: keep + # -- Names of the fields to limit. + names: {} +- ## Examples: +- # ClientUsername: drop ++ ## Examples: ++ # ClientUsername: drop + headers: + # -- Available modes: keep, drop, redact. + defaultmode: drop + # -- Names of the headers to limit. + names: {} +- ## Examples: +- # User-Agent: redact +- # Authorization: drop +- # Content-Type: keep ++ ## Examples: ++ # User-Agent: redact ++ # Authorization: drop ++ # Content-Type: keep + + metrics: + ## -- Prometheus is enabled by default. +@@ -308,118 +324,118 @@ metrics: + ## When manualRouting is true, it disables the default internal router in + ## order to allow creating a custom router for prometheus@internal service. + # manualRouting: true +-# datadog: +-# ## Address instructs exporter to send metrics to datadog-agent at this address. +-# address: "127.0.0.1:8125" +-# ## The interval used by the exporter to push metrics to datadog-agent. Default=10s +-# # pushInterval: 30s +-# ## The prefix to use for metrics collection. Default="traefik" +-# # prefix: traefik +-# ## Enable metrics on entry points. Default=true +-# # addEntryPointsLabels: false +-# ## Enable metrics on routers. Default=false +-# # addRoutersLabels: true +-# ## Enable metrics on services. Default=true +-# # addServicesLabels: false +-# influxdb: +-# ## Address instructs exporter to send metrics to influxdb at this address. +-# address: localhost:8089 +-# ## InfluxDB's address protocol (udp or http). Default="udp" +-# protocol: udp +-# ## InfluxDB database used when protocol is http. Default="" +-# # database: "" +-# ## InfluxDB retention policy used when protocol is http. Default="" +-# # retentionPolicy: "" +-# ## InfluxDB username (only with http). Default="" +-# # username: "" +-# ## InfluxDB password (only with http). Default="" +-# # password: "" +-# ## The interval used by the exporter to push metrics to influxdb. Default=10s +-# # pushInterval: 30s +-# ## Additional labels (influxdb tags) on all metrics. +-# # additionalLabels: +-# # env: production +-# # foo: bar +-# ## Enable metrics on entry points. Default=true +-# # addEntryPointsLabels: false +-# ## Enable metrics on routers. Default=false +-# # addRoutersLabels: true +-# ## Enable metrics on services. Default=true +-# # addServicesLabels: false +-# influxdb2: +-# ## Address instructs exporter to send metrics to influxdb v2 at this address. +-# address: localhost:8086 +-# ## Token with which to connect to InfluxDB v2. +-# token: xxx +-# ## Organisation where metrics will be stored. +-# org: "" +-# ## Bucket where metrics will be stored. +-# bucket: "" +-# ## The interval used by the exporter to push metrics to influxdb. Default=10s +-# # pushInterval: 30s +-# ## Additional labels (influxdb tags) on all metrics. +-# # additionalLabels: +-# # env: production +-# # foo: bar +-# ## Enable metrics on entry points. Default=true +-# # addEntryPointsLabels: false +-# ## Enable metrics on routers. Default=false +-# # addRoutersLabels: true +-# ## Enable metrics on services. Default=true +-# # addServicesLabels: false +-# statsd: +-# ## Address instructs exporter to send metrics to statsd at this address. +-# address: localhost:8125 +-# ## The interval used by the exporter to push metrics to influxdb. Default=10s +-# # pushInterval: 30s +-# ## The prefix to use for metrics collection. Default="traefik" +-# # prefix: traefik +-# ## Enable metrics on entry points. Default=true +-# # addEntryPointsLabels: false +-# ## Enable metrics on routers. Default=false +-# # addRoutersLabels: true +-# ## Enable metrics on services. Default=true +-# # addServicesLabels: false +-# openTelemetry: +-# ## Address of the OpenTelemetry Collector to send metrics to. +-# address: "localhost:4318" +-# ## Enable metrics on entry points. +-# addEntryPointsLabels: true +-# ## Enable metrics on routers. +-# addRoutersLabels: true +-# ## Enable metrics on services. +-# addServicesLabels: true +-# ## Explicit boundaries for Histogram data points. +-# explicitBoundaries: +-# - "0.1" +-# - "0.3" +-# - "1.2" +-# - "5.0" +-# ## Additional headers sent with metrics by the reporter to the OpenTelemetry Collector. +-# headers: +-# foo: bar +-# test: test +-# ## Allows reporter to send metrics to the OpenTelemetry Collector without using a secured protocol. +-# insecure: true +-# ## Interval at which metrics are sent to the OpenTelemetry Collector. +-# pushInterval: 10s +-# ## Allows to override the default URL path used for sending metrics. This option has no effect when using gRPC transport. +-# path: /foo/v1/traces +-# ## Defines the TLS configuration used by the reporter to send metrics to the OpenTelemetry Collector. +-# tls: +-# ## The path to the certificate authority, it defaults to the system bundle. +-# ca: path/to/ca.crt +-# ## The path to the public certificate. When using this option, setting the key option is required. +-# cert: path/to/foo.cert +-# ## The path to the private key. When using this option, setting the cert option is required. +-# key: path/to/key.key +-# ## If set to true, the TLS connection accepts any certificate presented by the server regardless of the hostnames it covers. +-# insecureSkipVerify: true +-# ## This instructs the reporter to send metrics to the OpenTelemetry Collector using gRPC. +-# grpc: true +- +-## -- enable optional CRDs for Prometheus Operator +-## ++ # datadog: ++ # ## Address instructs exporter to send metrics to datadog-agent at this address. ++ # address: "127.0.0.1:8125" ++ # ## The interval used by the exporter to push metrics to datadog-agent. Default=10s ++ # # pushInterval: 30s ++ # ## The prefix to use for metrics collection. Default="traefik" ++ # # prefix: traefik ++ # ## Enable metrics on entry points. Default=true ++ # # addEntryPointsLabels: false ++ # ## Enable metrics on routers. Default=false ++ # # addRoutersLabels: true ++ # ## Enable metrics on services. Default=true ++ # # addServicesLabels: false ++ # influxdb: ++ # ## Address instructs exporter to send metrics to influxdb at this address. ++ # address: localhost:8089 ++ # ## InfluxDB's address protocol (udp or http). Default="udp" ++ # protocol: udp ++ # ## InfluxDB database used when protocol is http. Default="" ++ # # database: "" ++ # ## InfluxDB retention policy used when protocol is http. Default="" ++ # # retentionPolicy: "" ++ # ## InfluxDB username (only with http). Default="" ++ # # username: "" ++ # ## InfluxDB password (only with http). Default="" ++ # # password: "" ++ # ## The interval used by the exporter to push metrics to influxdb. Default=10s ++ # # pushInterval: 30s ++ # ## Additional labels (influxdb tags) on all metrics. ++ # # additionalLabels: ++ # # env: production ++ # # foo: bar ++ # ## Enable metrics on entry points. Default=true ++ # # addEntryPointsLabels: false ++ # ## Enable metrics on routers. Default=false ++ # # addRoutersLabels: true ++ # ## Enable metrics on services. Default=true ++ # # addServicesLabels: false ++ # influxdb2: ++ # ## Address instructs exporter to send metrics to influxdb v2 at this address. ++ # address: localhost:8086 ++ # ## Token with which to connect to InfluxDB v2. ++ # token: xxx ++ # ## Organisation where metrics will be stored. ++ # org: "" ++ # ## Bucket where metrics will be stored. ++ # bucket: "" ++ # ## The interval used by the exporter to push metrics to influxdb. Default=10s ++ # # pushInterval: 30s ++ # ## Additional labels (influxdb tags) on all metrics. ++ # # additionalLabels: ++ # # env: production ++ # # foo: bar ++ # ## Enable metrics on entry points. Default=true ++ # # addEntryPointsLabels: false ++ # ## Enable metrics on routers. Default=false ++ # # addRoutersLabels: true ++ # ## Enable metrics on services. Default=true ++ # # addServicesLabels: false ++ # statsd: ++ # ## Address instructs exporter to send metrics to statsd at this address. ++ # address: localhost:8125 ++ # ## The interval used by the exporter to push metrics to influxdb. Default=10s ++ # # pushInterval: 30s ++ # ## The prefix to use for metrics collection. Default="traefik" ++ # # prefix: traefik ++ # ## Enable metrics on entry points. Default=true ++ # # addEntryPointsLabels: false ++ # ## Enable metrics on routers. Default=false ++ # # addRoutersLabels: true ++ # ## Enable metrics on services. Default=true ++ # # addServicesLabels: false ++ # openTelemetry: ++ # ## Address of the OpenTelemetry Collector to send metrics to. ++ # address: "localhost:4318" ++ # ## Enable metrics on entry points. ++ # addEntryPointsLabels: true ++ # ## Enable metrics on routers. ++ # addRoutersLabels: true ++ # ## Enable metrics on services. ++ # addServicesLabels: true ++ # ## Explicit boundaries for Histogram data points. ++ # explicitBoundaries: ++ # - "0.1" ++ # - "0.3" ++ # - "1.2" ++ # - "5.0" ++ # ## Additional headers sent with metrics by the reporter to the OpenTelemetry Collector. ++ # headers: ++ # foo: bar ++ # test: test ++ # ## Allows reporter to send metrics to the OpenTelemetry Collector without using a secured protocol. ++ # insecure: true ++ # ## Interval at which metrics are sent to the OpenTelemetry Collector. ++ # pushInterval: 10s ++ # ## Allows to override the default URL path used for sending metrics. This option has no effect when using gRPC transport. ++ # path: /foo/v1/traces ++ # ## Defines the TLS configuration used by the reporter to send metrics to the OpenTelemetry Collector. ++ # tls: ++ # ## The path to the certificate authority, it defaults to the system bundle. ++ # ca: path/to/ca.crt ++ # ## The path to the public certificate. When using this option, setting the key option is required. ++ # cert: path/to/foo.cert ++ # ## The path to the private key. When using this option, setting the cert option is required. ++ # key: path/to/key.key ++ # ## If set to true, the TLS connection accepts any certificate presented by the server regardless of the hostnames it covers. ++ # insecureSkipVerify: true ++ # ## This instructs the reporter to send metrics to the OpenTelemetry Collector using gRPC. ++ # grpc: true ++ ++ ## -- enable optional CRDs for Prometheus Operator ++ ## + ## Create a dedicated metrics service for use with ServiceMonitor + # service: + # enabled: false +@@ -470,55 +486,55 @@ metrics: + ## Tracing + # -- https://doc.traefik.io/traefik/observability/tracing/overview/ + tracing: {} +- # openTelemetry: # traefik v3+ only +- # grpc: {} +- # insecure: true +- # address: localhost:4317 +- # instana: +- # localAgentHost: 127.0.0.1 +- # localAgentPort: 42699 +- # logLevel: info +- # enableAutoProfile: true +- # datadog: +- # localAgentHostPort: 127.0.0.1:8126 +- # debug: false +- # globalTag: "" +- # prioritySampling: false +- # jaeger: +- # samplingServerURL: http://localhost:5778/sampling +- # samplingType: const +- # samplingParam: 1.0 +- # localAgentHostPort: 127.0.0.1:6831 +- # gen128Bit: false +- # propagation: jaeger +- # traceContextHeaderName: uber-trace-id +- # disableAttemptReconnecting: true +- # collector: +- # endpoint: "" +- # user: "" +- # password: "" +- # zipkin: +- # httpEndpoint: http://localhost:9411/api/v2/spans +- # sameSpan: false +- # id128Bit: true +- # sampleRate: 1.0 +- # haystack: +- # localAgentHost: 127.0.0.1 +- # localAgentPort: 35000 +- # globalTag: "" +- # traceIDHeaderName: "" +- # parentIDHeaderName: "" +- # spanIDHeaderName: "" +- # baggagePrefixHeaderName: "" +- # elastic: +- # serverURL: http://localhost:8200 +- # secretToken: "" +- # serviceEnvironment: "" ++# openTelemetry: # traefik v3+ only ++# grpc: {} ++# insecure: true ++# address: localhost:4317 ++# instana: ++# localAgentHost: 127.0.0.1 ++# localAgentPort: 42699 ++# logLevel: info ++# enableAutoProfile: true ++# datadog: ++# localAgentHostPort: 127.0.0.1:8126 ++# debug: false ++# globalTag: "" ++# prioritySampling: false ++# jaeger: ++# samplingServerURL: http://localhost:5778/sampling ++# samplingType: const ++# samplingParam: 1.0 ++# localAgentHostPort: 127.0.0.1:6831 ++# gen128Bit: false ++# propagation: jaeger ++# traceContextHeaderName: uber-trace-id ++# disableAttemptReconnecting: true ++# collector: ++# endpoint: "" ++# user: "" ++# password: "" ++# zipkin: ++# httpEndpoint: http://localhost:9411/api/v2/spans ++# sameSpan: false ++# id128Bit: true ++# sampleRate: 1.0 ++# haystack: ++# localAgentHost: 127.0.0.1 ++# localAgentPort: 35000 ++# globalTag: "" ++# traceIDHeaderName: "" ++# parentIDHeaderName: "" ++# spanIDHeaderName: "" ++# baggagePrefixHeaderName: "" ++# elastic: ++# serverURL: http://localhost:8200 ++# secretToken: "" ++# serviceEnvironment: "" + + # -- Global command arguments to be passed to all traefik's pods + globalArguments: +- - "--global.checknewversion" +- - "--global.sendanonymoususage" ++- "--global.checknewversion" ++- "--global.sendanonymoususage" + + # + # Configure Traefik static configuration +@@ -531,14 +547,14 @@ additionalArguments: [] + + # -- Environment variables to be passed to Traefik's binary + env: +- - name: POD_NAME +- valueFrom: +- fieldRef: +- fieldPath: metadata.name +- - name: POD_NAMESPACE +- valueFrom: +- fieldRef: +- fieldPath: metadata.namespace ++- name: POD_NAME ++ valueFrom: ++ fieldRef: ++ fieldPath: metadata.name ++- name: POD_NAMESPACE ++ valueFrom: ++ fieldRef: ++ fieldPath: metadata.namespace + # - name: SOME_VAR + # value: some-var-value + # - name: SOME_VAR_FROM_CONFIG_MAP +@@ -600,7 +616,10 @@ ports: + # Port Redirections + # Added in 2.2, you can make permanent redirects via entrypoints. + # https://docs.traefik.io/routing/entrypoints/#redirection +- # redirectTo: websecure ++ # redirectTo: ++ # port: websecure ++ # (Optional) ++ # priority: 10 + # + # Trust forwarded headers information (X-Forwarded-*). + # forwardedHeaders: +@@ -638,14 +657,14 @@ ports: + # advertisedPort: 4443 + # + ## -- Trust forwarded headers information (X-Forwarded-*). +- #forwardedHeaders: +- # trustedIPs: [] +- # insecure: false ++ # forwardedHeaders: ++ # trustedIPs: [] ++ # insecure: false + # + ## -- Enable the Proxy Protocol header parsing for the entry point +- #proxyProtocol: +- # trustedIPs: [] +- # insecure: false ++ # proxyProtocol: ++ # trustedIPs: [] ++ # insecure: false + # + ## Set TLS at the entrypoint + ## https://doc.traefik.io/traefik/routing/entrypoints/#tls +@@ -728,16 +747,16 @@ service: + # -- Additional entries here will be added to the service spec. + # -- Cannot contain type, selector or ports entries. + spec: {} +- # externalTrafficPolicy: Cluster +- # loadBalancerIP: "1.2.3.4" +- # clusterIP: "2.3.4.5" ++ # externalTrafficPolicy: Cluster ++ # loadBalancerIP: "1.2.3.4" ++ # clusterIP: "2.3.4.5" + loadBalancerSourceRanges: [] +- # - 192.168.0.1/32 +- # - 172.16.0.0/16 ++ # - 192.168.0.1/32 ++ # - 172.16.0.0/16 + ## -- Class of the load balancer implementation + # loadBalancerClass: service.k8s.aws/nlb + externalIPs: [] +- # - 1.2.3.4 ++ # - 1.2.3.4 + ## One of SingleStack, PreferDualStack, or RequireDualStack. + # ipFamilyPolicy: SingleStack + ## List of IP families (e.g. IPv4 and/or IPv6). +@@ -789,7 +808,7 @@ persistence: + # It can be used to store TLS certificates, see `storage` in certResolvers + enabled: false + name: data +-# existingClaim: "" ++ # existingClaim: "" + accessMode: ReadWriteOnce + size: 128Mi + # storageClass: "" +@@ -852,12 +871,12 @@ serviceAccountAnnotations: {} + + # -- The resources parameter defines CPU and memory requirements and limits for Traefik's containers. + resources: {} +- # requests: +- # cpu: "100m" +- # memory: "50Mi" +- # limits: +- # cpu: "300m" +- # memory: "150Mi" ++# requests: ++# cpu: "100m" ++# memory: "50Mi" ++# limits: ++# cpu: "300m" ++# memory: "150Mi" + + # -- This example pod anti-affinity forces the scheduler to put traefik pods + # -- on nodes where no other traefik pods are scheduled. +``` + +## 24.0.0 ![AppVersion: v2.10.4](https://img.shields.io/static/v1?label=AppVersion&message=v2.10.4&color=success&logo=) ![Kubernetes: >=1.16.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.16.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2023-08-10 + +* fix: 💥 BREAKING CHANGE on healthchecks and traefik port +* fix: tracing.opentelemetry.tls is optional for all values +* fix: http3 support broken when advertisedPort set +* feat: multi namespace RBAC manifests +* chore(tests): 🔧 fix typo on tracing test +* chore(release): 🚀 publish v24.0.0 +* chore(deps): update docker.io/helmunittest/helm-unittest docker tag to v3.12.2 + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 947ba56..aeec85c 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -28,6 +28,13 @@ deployment: + terminationGracePeriodSeconds: 60 + # -- The minimum number of seconds Traefik needs to be up and running before the DaemonSet/Deployment controller considers it available + minReadySeconds: 0 ++ ## Override the liveness/readiness port. This is useful to integrate traefik ++ ## with an external Load Balancer that performs healthchecks. ++ ## Default: ports.traefik.port ++ # healthchecksPort: 9000 ++ ## Override the liveness/readiness scheme. Useful for getting ping to ++ ## respond on websecure entryPoint. ++ # healthchecksScheme: HTTPS + # -- Additional deployment annotations (e.g. for jaeger-operator sidecar injection) + annotations: {} + # -- Additional deployment labels (e.g. for filtering deployment by custom labels) +@@ -112,7 +119,7 @@ experimental: + #This value is no longer used, set the image.tag to a semver higher than 3.0, e.g. "v3.0.0-beta3" + #v3: + # -- Enable traefik version 3 +- # enabled: false ++ # enabled: false + plugins: + # -- Enable traefik experimental plugins + enabled: false +@@ -564,15 +571,6 @@ ports: + # only. + # hostIP: 192.168.100.10 + +- # Override the liveness/readiness port. This is useful to integrate traefik +- # with an external Load Balancer that performs healthchecks. +- # Default: ports.traefik.port +- # healthchecksPort: 9000 +- +- # Override the liveness/readiness scheme. Useful for getting ping to +- # respond on websecure entryPoint. +- # healthchecksScheme: HTTPS +- + # Defines whether the port is exposed if service.type is LoadBalancer or + # NodePort. + # +@@ -877,7 +875,7 @@ affinity: {} + nodeSelector: {} + # -- Tolerations allow the scheduler to schedule pods with matching taints. + tolerations: [] +-# -- You can use topology spread constraints to control ++# -- You can use topology spread constraints to control + # how Pods are spread across your cluster among failure-domains. + topologySpreadConstraints: [] + # This example topologySpreadConstraints forces the scheduler to put traefik pods +``` + +## 23.2.0 ![AppVersion: v2.10.4](https://img.shields.io/static/v1?label=AppVersion&message=v2.10.4&color=success&logo=) ![Kubernetes: >=1.16.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.16.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2023-07-27 + +* ⬆️ Upgrade traefik Docker tag to v2.10.3 +* release: :rocket: publish v23.2.0 +* fix: 🐛 update traefik.containo.us CRDs to v2.10 +* fix: 🐛 traefik or metrics port can be disabled +* fix: ingressclass name should be customizable (#864) +* feat: ✨ add support for traefik v3.0.0-beta3 and openTelemetry +* feat: disable allowPrivilegeEscalation +* feat: add pod_name as default in values.yaml +* chore(tests): 🔧 use more accurate asserts on refactor'd isNull test +* chore(deps): update traefik docker tag to v2.10.4 +* chore(deps): update docker.io/helmunittest/helm-unittest docker tag to v3.11.3 + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 345bbd8..947ba56 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -105,12 +105,14 @@ podDisruptionBudget: + ingressClass: + enabled: true + isDefaultClass: true ++ # name: my-custom-class + + # Traefik experimental features + experimental: +- v3: ++ #This value is no longer used, set the image.tag to a semver higher than 3.0, e.g. "v3.0.0-beta3" ++ #v3: + # -- Enable traefik version 3 +- enabled: false ++ # enabled: false + plugins: + # -- Enable traefik experimental plugins + enabled: false +@@ -461,6 +463,10 @@ metrics: + ## Tracing + # -- https://doc.traefik.io/traefik/observability/tracing/overview/ + tracing: {} ++ # openTelemetry: # traefik v3+ only ++ # grpc: {} ++ # insecure: true ++ # address: localhost:4317 + # instana: + # localAgentHost: 127.0.0.1 + # localAgentPort: 42699 +@@ -517,7 +523,15 @@ additionalArguments: [] + # - "--log.level=DEBUG" + + # -- Environment variables to be passed to Traefik's binary +-env: [] ++env: ++ - name: POD_NAME ++ valueFrom: ++ fieldRef: ++ fieldPath: metadata.name ++ - name: POD_NAMESPACE ++ valueFrom: ++ fieldRef: ++ fieldPath: metadata.namespace + # - name: SOME_VAR + # value: some-var-value + # - name: SOME_VAR_FROM_CONFIG_MAP +@@ -563,7 +577,7 @@ ports: + # NodePort. + # + # -- You SHOULD NOT expose the traefik port on production deployments. +- # If you want to access it from outside of your cluster, ++ # If you want to access it from outside your cluster, + # use `kubectl port-forward` or create a secure ingress + expose: false + # -- The exposed port for this service +@@ -571,7 +585,7 @@ ports: + # -- The port protocol (TCP/UDP) + protocol: TCP + web: +- ## -- Enable this entrypoint as a default entrypoint. When a service doesn't explicity set an entrypoint it will only use this entrypoint. ++ ## -- Enable this entrypoint as a default entrypoint. When a service doesn't explicitly set an entrypoint it will only use this entrypoint. + # asDefault: true + port: 8000 + # hostPort: 8000 +@@ -600,7 +614,7 @@ ports: + # trustedIPs: [] + # insecure: false + websecure: +- ## -- Enable this entrypoint as a default entrypoint. When a service doesn't explicity set an entrypoint it will only use this entrypoint. ++ ## -- Enable this entrypoint as a default entrypoint. When a service doesn't explicitly set an entrypoint it will only use this entrypoint. + # asDefault: true + port: 8443 + # hostPort: 8443 +@@ -666,7 +680,7 @@ ports: + # NodePort. + # + # -- You may not want to expose the metrics port on production deployments. +- # If you want to access it from outside of your cluster, ++ # If you want to access it from outside your cluster, + # use `kubectl port-forward` or create a secure ingress + expose: false + # -- The exposed port for this service +@@ -880,14 +894,15 @@ topologySpreadConstraints: [] + priorityClassName: "" + + # -- Set the container security context +-# -- To run the container with ports below 1024 this will need to be adjust to run as root ++# -- To run the container with ports below 1024 this will need to be adjusted to run as root + securityContext: + capabilities: + drop: [ALL] + readOnlyRootFilesystem: true ++ allowPrivilegeEscalation: false + + podSecurityContext: +- # /!\ When setting fsGroup, Kubernetes will recursively changes ownership and ++ # /!\ When setting fsGroup, Kubernetes will recursively change ownership and + # permissions for the contents of each volume to match the fsGroup. This can + # be an issue when storing sensitive content like TLS Certificates /!\ + # fsGroup: 65532 +``` + +## 23.1.0 ![AppVersion: v2.10.1](https://img.shields.io/static/v1?label=AppVersion&message=v2.10.1&color=success&logo=) ![Kubernetes: >=1.16.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.16.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2023-06-06 + +* release: 🚀 publish v23.1.0 +* fix: 🐛 use k8s version for hpa api version +* fix: 🐛 http3 support on traefik v3 +* fix: use `targetPort` instead of `port` on ServiceMonitor +* feat: ➖ remove Traefik Hub v1 integration +* feat: ✨ add a warning when labelSelector don't match +* feat: common labels for all resources +* feat: allow specifying service loadBalancerClass +* feat: add optional `appProtocol` field on Service ports +* doc: added values README via helm-docs cli + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 71273cc..345bbd8 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -1,70 +1,56 @@ + # Default values for Traefik + image: ++ # -- Traefik image host registry + registry: docker.io ++ # -- Traefik image repository + repository: traefik +- # defaults to appVersion ++ # -- defaults to appVersion + tag: "" ++ # -- Traefik image pull policy + pullPolicy: IfNotPresent + +-# +-# Configure integration with Traefik Hub +-# +-hub: +- ## Enabling Hub will: +- # * enable Traefik Hub integration on Traefik +- # * add `traefikhub-tunl` endpoint +- # * enable Prometheus metrics with addRoutersLabels +- # * enable allowExternalNameServices on KubernetesIngress provider +- # * enable allowCrossNamespace on KubernetesCRD provider +- # * add an internal (ClusterIP) Service, dedicated for Traefik Hub +- enabled: false +- ## Default port can be changed +- # tunnelPort: 9901 +- ## TLS is optional. Insecure is mutually exclusive with any other options +- # tls: +- # insecure: false +- # ca: "/path/to/ca.pem" +- # cert: "/path/to/cert.pem" +- # key: "/path/to/key.pem" ++# -- Add additional label to all resources ++commonLabels: {} + + # + # Configure the deployment + # + deployment: ++ # -- Enable deployment + enabled: true +- # Can be either Deployment or DaemonSet ++ # -- Deployment or DaemonSet + kind: Deployment +- # Number of pods of the deployment (only applies when kind == Deployment) ++ # -- Number of pods of the deployment (only applies when kind == Deployment) + replicas: 1 +- # Number of old history to retain to allow rollback (If not set, default Kubernetes value is set to 10) ++ # -- Number of old history to retain to allow rollback (If not set, default Kubernetes value is set to 10) + # revisionHistoryLimit: 1 +- # Amount of time (in seconds) before Kubernetes will send the SIGKILL signal if Traefik does not shut down ++ # -- Amount of time (in seconds) before Kubernetes will send the SIGKILL signal if Traefik does not shut down + terminationGracePeriodSeconds: 60 +- # The minimum number of seconds Traefik needs to be up and running before the DaemonSet/Deployment controller considers it available ++ # -- The minimum number of seconds Traefik needs to be up and running before the DaemonSet/Deployment controller considers it available + minReadySeconds: 0 +- # Additional deployment annotations (e.g. for jaeger-operator sidecar injection) ++ # -- Additional deployment annotations (e.g. for jaeger-operator sidecar injection) + annotations: {} +- # Additional deployment labels (e.g. for filtering deployment by custom labels) ++ # -- Additional deployment labels (e.g. for filtering deployment by custom labels) + labels: {} +- # Additional pod annotations (e.g. for mesh injection or prometheus scraping) ++ # -- Additional pod annotations (e.g. for mesh injection or prometheus scraping) + podAnnotations: {} +- # Additional Pod labels (e.g. for filtering Pod by custom labels) ++ # -- Additional Pod labels (e.g. for filtering Pod by custom labels) + podLabels: {} +- # Additional containers (e.g. for metric offloading sidecars) ++ # -- Additional containers (e.g. for metric offloading sidecars) + additionalContainers: [] + # https://docs.datadoghq.com/developers/dogstatsd/unix_socket/?tab=host + # - name: socat-proxy +- # image: alpine/socat:1.0.5 +- # args: ["-s", "-u", "udp-recv:8125", "unix-sendto:/socket/socket"] +- # volumeMounts: +- # - name: dsdsocket +- # mountPath: /socket +- # Additional volumes available for use with initContainers and additionalContainers ++ # image: alpine/socat:1.0.5 ++ # args: ["-s", "-u", "udp-recv:8125", "unix-sendto:/socket/socket"] ++ # volumeMounts: ++ # - name: dsdsocket ++ # mountPath: /socket ++ # -- Additional volumes available for use with initContainers and additionalContainers + additionalVolumes: [] + # - name: dsdsocket + # hostPath: + # path: /var/run/statsd-exporter +- # Additional initContainers (e.g. for setting file permission as shown below) ++ # -- Additional initContainers (e.g. for setting file permission as shown below) + initContainers: [] + # The "volume-permissions" init container is required if you run into permission issues. + # Related issue: https://github.com/traefik/traefik-helm-chart/issues/396 +@@ -78,9 +64,9 @@ deployment: + # volumeMounts: + # - name: data + # mountPath: /data +- # Use process namespace sharing ++ # -- Use process namespace sharing + shareProcessNamespace: false +- # Custom pod DNS policy. Apply if `hostNetwork: true` ++ # -- Custom pod DNS policy. Apply if `hostNetwork: true` + # dnsPolicy: ClusterFirstWithHostNet + dnsConfig: {} + # nameservers: +@@ -92,10 +78,10 @@ deployment: + # - name: ndots + # value: "2" + # - name: edns0 +- # Additional imagePullSecrets ++ # -- Additional imagePullSecrets + imagePullSecrets: [] + # - name: myRegistryKeySecretName +- # Pod lifecycle actions ++ # -- Pod lifecycle actions + lifecycle: {} + # preStop: + # exec: +@@ -107,7 +93,7 @@ deployment: + # host: localhost + # scheme: HTTP + +-# Pod disruption budget ++# -- Pod disruption budget + podDisruptionBudget: + enabled: false + # maxUnavailable: 1 +@@ -115,93 +101,112 @@ podDisruptionBudget: + # minAvailable: 0 + # minAvailable: 25% + +-# Create a default IngressClass for Traefik ++# -- Create a default IngressClass for Traefik + ingressClass: + enabled: true + isDefaultClass: true + +-# Enable experimental features ++# Traefik experimental features + experimental: + v3: ++ # -- Enable traefik version 3 + enabled: false + plugins: ++ # -- Enable traefik experimental plugins + enabled: false + kubernetesGateway: ++ # -- Enable traefik experimental GatewayClass CRD + enabled: false + gateway: ++ # -- Enable traefik regular kubernetes gateway + enabled: true + # certificate: + # group: "core" + # kind: "Secret" + # name: "mysecret" +- # By default, Gateway would be created to the Namespace you are deploying Traefik to. ++ # -- By default, Gateway would be created to the Namespace you are deploying Traefik to. + # You may create that Gateway in another namespace, setting its name below: + # namespace: default + # Additional gateway annotations (e.g. for cert-manager.io/issuer) + # annotations: + # cert-manager.io/issuer: letsencrypt + +-# Create an IngressRoute for the dashboard ++## Create an IngressRoute for the dashboard + ingressRoute: + dashboard: ++ # -- Create an IngressRoute for the dashboard + enabled: true +- # Additional ingressRoute annotations (e.g. for kubernetes.io/ingress.class) ++ # -- Additional ingressRoute annotations (e.g. for kubernetes.io/ingress.class) + annotations: {} +- # Additional ingressRoute labels (e.g. for filtering IngressRoute by custom labels) ++ # -- Additional ingressRoute labels (e.g. for filtering IngressRoute by custom labels) + labels: {} +- # The router match rule used for the dashboard ingressRoute ++ # -- The router match rule used for the dashboard ingressRoute + matchRule: PathPrefix(`/dashboard`) || PathPrefix(`/api`) +- # Specify the allowed entrypoints to use for the dashboard ingress route, (e.g. traefik, web, websecure). ++ # -- Specify the allowed entrypoints to use for the dashboard ingress route, (e.g. traefik, web, websecure). + # By default, it's using traefik entrypoint, which is not exposed. + # /!\ Do not expose your dashboard without any protection over the internet /!\ + entryPoints: ["traefik"] +- # Additional ingressRoute middlewares (e.g. for authentication) ++ # -- Additional ingressRoute middlewares (e.g. for authentication) + middlewares: [] +- # TLS options (e.g. secret containing certificate) ++ # -- TLS options (e.g. secret containing certificate) + tls: {} + +-# Customize updateStrategy of traefik pods + updateStrategy: ++ # -- Customize updateStrategy: RollingUpdate or OnDelete + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + +-# Customize liveness and readiness probe values. + readinessProbe: ++ # -- The number of consecutive failures allowed before considering the probe as failed. + failureThreshold: 1 ++ # -- The number of seconds to wait before starting the first probe. + initialDelaySeconds: 2 ++ # -- The number of seconds to wait between consecutive probes. + periodSeconds: 10 ++ # -- The minimum consecutive successes required to consider the probe successful. + successThreshold: 1 ++ # -- The number of seconds to wait for a probe response before considering it as failed. + timeoutSeconds: 2 +- + livenessProbe: ++ # -- The number of consecutive failures allowed before considering the probe as failed. + failureThreshold: 3 ++ # -- The number of seconds to wait before starting the first probe. + initialDelaySeconds: 2 ++ # -- The number of seconds to wait between consecutive probes. + periodSeconds: 10 ++ # -- The minimum consecutive successes required to consider the probe successful. + successThreshold: 1 ++ # -- The number of seconds to wait for a probe response before considering it as failed. + timeoutSeconds: 2 + +-# +-# Configure providers +-# + providers: + kubernetesCRD: ++ # -- Load Kubernetes IngressRoute provider + enabled: true ++ # -- Allows IngressRoute to reference resources in namespace other than theirs + allowCrossNamespace: false ++ # -- Allows to reference ExternalName services in IngressRoute + allowExternalNameServices: false ++ # -- Allows to return 503 when there is no endpoints available + allowEmptyServices: false + # ingressClass: traefik-internal + # labelSelector: environment=production,method=traefik ++ # -- Array of namespaces to watch. If left empty, Traefik watches all namespaces. + namespaces: [] + # - "default" + + kubernetesIngress: ++ # -- Load Kubernetes IngressRoute provider + enabled: true ++ # -- Allows to reference ExternalName services in Ingress + allowExternalNameServices: false ++ # -- Allows to return 503 when there is no endpoints available + allowEmptyServices: false + # ingressClass: traefik-internal + # labelSelector: environment=production,method=traefik ++ # -- Array of namespaces to watch. If left empty, Traefik watches all namespaces. + namespaces: [] + # - "default" + # IP used for Kubernetes Ingress endpoints +@@ -212,13 +217,13 @@ providers: + # pathOverride: "" + + # +-# Add volumes to the traefik pod. The volume name will be passed to tpl. ++# -- Add volumes to the traefik pod. The volume name will be passed to tpl. + # This can be used to mount a cert pair or a configmap that holds a config.toml file. + # After the volume has been mounted, add the configs into traefik by using the `additionalArguments` list below, eg: +-# additionalArguments: ++# `additionalArguments: + # - "--providers.file.filename=/config/dynamic.toml" + # - "--ping" +-# - "--ping.entrypoint=web" ++# - "--ping.entrypoint=web"` + volumes: [] + # - name: public-cert + # mountPath: "/certs" +@@ -227,25 +232,22 @@ volumes: [] + # mountPath: "/config" + # type: configMap + +-# Additional volumeMounts to add to the Traefik container ++# -- Additional volumeMounts to add to the Traefik container + additionalVolumeMounts: [] +- # For instance when using a logshipper for access logs ++ # -- For instance when using a logshipper for access logs + # - name: traefik-logs + # mountPath: /var/log/traefik + +-## Logs +-## https://docs.traefik.io/observability/logs/ + logs: +- ## Traefik logs concern everything that happens to Traefik itself (startup, configuration, events, shutdown, and so on). + general: +- # By default, the logs use a text format (common), but you can ++ # -- By default, the logs use a text format (common), but you can + # also ask for the json format in the format option + # format: json + # By default, the level is set to ERROR. +- # Alternative logging levels are DEBUG, PANIC, FATAL, ERROR, WARN, and INFO. ++ # -- Alternative logging levels are DEBUG, PANIC, FATAL, ERROR, WARN, and INFO. + level: ERROR + access: +- # To enable access logs ++ # -- To enable access logs + enabled: false + ## By default, logs are written using the Common Log Format (CLF) on stdout. + ## To write logs in JSON, use json in the format option. +@@ -256,21 +258,24 @@ logs: + ## This option represents the number of log lines Traefik will keep in memory before writing + ## them to the selected output. In some cases, this option can greatly help performances. + # bufferingSize: 100 +- ## Filtering https://docs.traefik.io/observability/access-logs/#filtering ++ ## Filtering ++ # -- https://docs.traefik.io/observability/access-logs/#filtering + filters: {} + # statuscodes: "200,300-302" + # retryattempts: true + # minduration: 10ms +- ## Fields +- ## https://docs.traefik.io/observability/access-logs/#limiting-the-fieldsincluding-headers + fields: + general: ++ # -- Available modes: keep, drop, redact. + defaultmode: keep ++ # -- Names of the fields to limit. + names: {} + ## Examples: + # ClientUsername: drop + headers: ++ # -- Available modes: keep, drop, redact. + defaultmode: drop ++ # -- Names of the headers to limit. + names: {} + ## Examples: + # User-Agent: redact +@@ -278,10 +283,10 @@ logs: + # Content-Type: keep + + metrics: +- ## Prometheus is enabled by default. +- ## It can be disabled by setting "prometheus: null" ++ ## -- Prometheus is enabled by default. ++ ## -- It can be disabled by setting "prometheus: null" + prometheus: +- ## Entry point used to expose metrics. ++ # -- Entry point used to expose metrics. + entryPoint: metrics + ## Enable metrics on entry points. Default=true + # addEntryPointsLabels: false +@@ -404,11 +409,9 @@ metrics: + # ## This instructs the reporter to send metrics to the OpenTelemetry Collector using gRPC. + # grpc: true + +-## +-## enable optional CRDs for Prometheus Operator ++## -- enable optional CRDs for Prometheus Operator + ## + ## Create a dedicated metrics service for use with ServiceMonitor +- ## When hub.enabled is set to true, it's not needed: it will use hub service. + # service: + # enabled: false + # labels: {} +@@ -455,6 +458,8 @@ metrics: + # summary: "Traefik Down" + # description: "{{ $labels.pod }} on {{ $labels.nodename }} is down" + ++## Tracing ++# -- https://doc.traefik.io/traefik/observability/tracing/overview/ + tracing: {} + # instana: + # localAgentHost: 127.0.0.1 +@@ -497,20 +502,21 @@ tracing: {} + # secretToken: "" + # serviceEnvironment: "" + ++# -- Global command arguments to be passed to all traefik's pods + globalArguments: + - "--global.checknewversion" + - "--global.sendanonymoususage" + + # + # Configure Traefik static configuration +-# Additional arguments to be passed at Traefik's binary ++# -- Additional arguments to be passed at Traefik's binary + # All available options available on https://docs.traefik.io/reference/static-configuration/cli/ + ## Use curly braces to pass values: `helm install --set="additionalArguments={--providers.kubernetesingress.ingressclass=traefik-internal,--log.level=DEBUG}"` + additionalArguments: [] + # - "--providers.kubernetesingress.ingressclass=traefik-internal" + # - "--log.level=DEBUG" + +-# Environment variables to be passed to Traefik's binary ++# -- Environment variables to be passed to Traefik's binary + env: [] + # - name: SOME_VAR + # value: some-var-value +@@ -525,22 +531,20 @@ env: [] + # name: secret-name + # key: secret-key + ++# -- Environment variables to be passed to Traefik's binary from configMaps or secrets + envFrom: [] + # - configMapRef: + # name: config-map-name + # - secretRef: + # name: secret-name + +-# Configure ports + ports: +- # The name of this one can't be changed as it is used for the readiness and +- # liveness probes, but you can adjust its config to your liking + traefik: + port: 9000 +- # Use hostPort if set. ++ # -- Use hostPort if set. + # hostPort: 9000 + # +- # Use hostIP if set. If not set, Kubernetes will default to 0.0.0.0, which ++ # -- Use hostIP if set. If not set, Kubernetes will default to 0.0.0.0, which + # means it's listening on all your interfaces and all your IPs. You may want + # to set this value if you need traefik to listen on specific interface + # only. +@@ -558,27 +562,27 @@ ports: + # Defines whether the port is exposed if service.type is LoadBalancer or + # NodePort. + # +- # You SHOULD NOT expose the traefik port on production deployments. ++ # -- You SHOULD NOT expose the traefik port on production deployments. + # If you want to access it from outside of your cluster, + # use `kubectl port-forward` or create a secure ingress + expose: false +- # The exposed port for this service ++ # -- The exposed port for this service + exposedPort: 9000 +- # The port protocol (TCP/UDP) ++ # -- The port protocol (TCP/UDP) + protocol: TCP + web: +- ## Enable this entrypoint as a default entrypoint. When a service doesn't explicity set an entrypoint it will only use this entrypoint. ++ ## -- Enable this entrypoint as a default entrypoint. When a service doesn't explicity set an entrypoint it will only use this entrypoint. + # asDefault: true + port: 8000 + # hostPort: 8000 + # containerPort: 8000 + expose: true + exposedPort: 80 +- ## Different target traefik port on the cluster, useful for IP type LB ++ ## -- Different target traefik port on the cluster, useful for IP type LB + # targetPort: 80 + # The port protocol (TCP/UDP) + protocol: TCP +- # Use nodeport if set. This is useful if you have configured Traefik in a ++ # -- Use nodeport if set. This is useful if you have configured Traefik in a + # LoadBalancer. + # nodePort: 32080 + # Port Redirections +@@ -596,20 +600,22 @@ ports: + # trustedIPs: [] + # insecure: false + websecure: +- ## Enable this entrypoint as a default entrypoint. When a service doesn't explicity set an entrypoint it will only use this entrypoint. ++ ## -- Enable this entrypoint as a default entrypoint. When a service doesn't explicity set an entrypoint it will only use this entrypoint. + # asDefault: true + port: 8443 + # hostPort: 8443 + # containerPort: 8443 + expose: true + exposedPort: 443 +- ## Different target traefik port on the cluster, useful for IP type LB ++ ## -- Different target traefik port on the cluster, useful for IP type LB + # targetPort: 80 +- ## The port protocol (TCP/UDP) ++ ## -- The port protocol (TCP/UDP) + protocol: TCP + # nodePort: 32443 ++ ## -- Specify an application protocol. This may be used as a hint for a Layer 7 load balancer. ++ # appProtocol: https + # +- ## Enable HTTP/3 on the entrypoint ++ ## -- Enable HTTP/3 on the entrypoint + ## Enabling it will also enable http3 experimental feature + ## https://doc.traefik.io/traefik/routing/entrypoints/#http3 + ## There are known limitations when trying to listen on same ports for +@@ -619,12 +625,12 @@ ports: + enabled: false + # advertisedPort: 4443 + # +- ## Trust forwarded headers information (X-Forwarded-*). ++ ## -- Trust forwarded headers information (X-Forwarded-*). + #forwardedHeaders: + # trustedIPs: [] + # insecure: false + # +- ## Enable the Proxy Protocol header parsing for the entry point ++ ## -- Enable the Proxy Protocol header parsing for the entry point + #proxyProtocol: + # trustedIPs: [] + # insecure: false +@@ -642,33 +648,33 @@ ports: + # - foo.example.com + # - bar.example.com + # +- # One can apply Middlewares on an entrypoint ++ # -- One can apply Middlewares on an entrypoint + # https://doc.traefik.io/traefik/middlewares/overview/ + # https://doc.traefik.io/traefik/routing/entrypoints/#middlewares +- # /!\ It introduces here a link between your static configuration and your dynamic configuration /!\ ++ # -- /!\ It introduces here a link between your static configuration and your dynamic configuration /!\ + # It follows the provider naming convention: https://doc.traefik.io/traefik/providers/overview/#provider-namespace + # middlewares: + # - namespace-name1@kubernetescrd + # - namespace-name2@kubernetescrd + middlewares: [] + metrics: +- # When using hostNetwork, use another port to avoid conflict with node exporter: ++ # -- When using hostNetwork, use another port to avoid conflict with node exporter: + # https://github.com/prometheus/prometheus/wiki/Default-port-allocations + port: 9100 + # hostPort: 9100 + # Defines whether the port is exposed if service.type is LoadBalancer or + # NodePort. + # +- # You may not want to expose the metrics port on production deployments. ++ # -- You may not want to expose the metrics port on production deployments. + # If you want to access it from outside of your cluster, + # use `kubectl port-forward` or create a secure ingress + expose: false +- # The exposed port for this service ++ # -- The exposed port for this service + exposedPort: 9100 +- # The port protocol (TCP/UDP) ++ # -- The port protocol (TCP/UDP) + protocol: TCP + +-# TLS Options are created as TLSOption CRDs ++# -- TLS Options are created as TLSOption CRDs + # https://doc.traefik.io/traefik/https/tls/#tls-options + # When using `labelSelector`, you'll need to set labels on tlsOption accordingly. + # Example: +@@ -684,7 +690,7 @@ ports: + # - CurveP384 + tlsOptions: {} + +-# TLS Store are created as TLSStore CRDs. This is useful if you want to set a default certificate ++# -- TLS Store are created as TLSStore CRDs. This is useful if you want to set a default certificate + # https://doc.traefik.io/traefik/https/tls/#default-certificate + # Example: + # tlsStore: +@@ -693,24 +699,22 @@ tlsOptions: {} + # secretName: tls-cert + tlsStore: {} + +-# Options for the main traefik service, where the entrypoints traffic comes +-# from. + service: + enabled: true +- ## Single service is using `MixedProtocolLBService` feature gate. +- ## When set to false, it will create two Service, one for TCP and one for UDP. ++ ## -- Single service is using `MixedProtocolLBService` feature gate. ++ ## -- When set to false, it will create two Service, one for TCP and one for UDP. + single: true + type: LoadBalancer +- # Additional annotations applied to both TCP and UDP services (e.g. for cloud provider specific config) ++ # -- Additional annotations applied to both TCP and UDP services (e.g. for cloud provider specific config) + annotations: {} +- # Additional annotations for TCP service only ++ # -- Additional annotations for TCP service only + annotationsTCP: {} +- # Additional annotations for UDP service only ++ # -- Additional annotations for UDP service only + annotationsUDP: {} +- # Additional service labels (e.g. for filtering Service by custom labels) ++ # -- Additional service labels (e.g. for filtering Service by custom labels) + labels: {} +- # Additional entries here will be added to the service spec. +- # Cannot contain type, selector or ports entries. ++ # -- Additional entries here will be added to the service spec. ++ # -- Cannot contain type, selector or ports entries. + spec: {} + # externalTrafficPolicy: Cluster + # loadBalancerIP: "1.2.3.4" +@@ -718,6 +722,8 @@ service: + loadBalancerSourceRanges: [] + # - 192.168.0.1/32 + # - 172.16.0.0/16 ++ ## -- Class of the load balancer implementation ++ # loadBalancerClass: service.k8s.aws/nlb + externalIPs: [] + # - 1.2.3.4 + ## One of SingleStack, PreferDualStack, or RequireDualStack. +@@ -728,7 +734,7 @@ service: + # - IPv4 + # - IPv6 + ## +- ## An additionnal and optional internal Service. ++ ## -- An additionnal and optional internal Service. + ## Same parameters as external Service + # internal: + # type: ClusterIP +@@ -739,9 +745,8 @@ service: + # # externalIPs: [] + # # ipFamilies: [ "IPv4","IPv6" ] + +-## Create HorizontalPodAutoscaler object. +-## + autoscaling: ++ # -- Create HorizontalPodAutoscaler object. + enabled: false + # minReplicas: 1 + # maxReplicas: 10 +@@ -766,10 +771,10 @@ autoscaling: + # value: 1 + # periodSeconds: 60 + +-# Enable persistence using Persistent Volume Claims +-# ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ +-# It can be used to store TLS certificates, see `storage` in certResolvers + persistence: ++ # -- Enable persistence using Persistent Volume Claims ++ # ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ ++ # It can be used to store TLS certificates, see `storage` in certResolvers + enabled: false + name: data + # existingClaim: "" +@@ -779,8 +784,10 @@ persistence: + # volumeName: "" + path: /data + annotations: {} +- # subPath: "" # only mount a subpath of the Volume into the pod ++ # -- Only mount a subpath of the Volume into the pod ++ # subPath: "" + ++# -- Certificates resolvers configuration + certResolvers: {} + # letsencrypt: + # # for challenge options cf. https://doc.traefik.io/traefik/https/acme/ +@@ -802,13 +809,13 @@ certResolvers: {} + # # It has to match the path with a persistent volume + # storage: /data/acme.json + +-# If hostNetwork is true, runs traefik in the host network namespace ++# -- If hostNetwork is true, runs traefik in the host network namespace + # To prevent unschedulabel pods due to port collisions, if hostNetwork=true + # and replicas>1, a pod anti-affinity is recommended and will be set if the + # affinity is left as default. + hostNetwork: false + +-# Whether Role Based Access Control objects like roles and rolebindings should be created ++# -- Whether Role Based Access Control objects like roles and rolebindings should be created + rbac: + enabled: true + # If set to false, installs ClusterRole and ClusterRoleBinding so Traefik can be used across namespaces. +@@ -818,19 +825,20 @@ rbac: + # https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles + # aggregateTo: [ "admin" ] + +-# Enable to create a PodSecurityPolicy and assign it to the Service Account via RoleBinding or ClusterRoleBinding ++# -- Enable to create a PodSecurityPolicy and assign it to the Service Account via RoleBinding or ClusterRoleBinding + podSecurityPolicy: + enabled: false + +-# The service account the pods will use to interact with the Kubernetes API ++# -- The service account the pods will use to interact with the Kubernetes API + serviceAccount: + # If set, an existing service account is used + # If not set, a service account is created automatically using the fullname template + name: "" + +-# Additional serviceAccount annotations (e.g. for oidc authentication) ++# -- Additional serviceAccount annotations (e.g. for oidc authentication) + serviceAccountAnnotations: {} + ++# -- The resources parameter defines CPU and memory requirements and limits for Traefik's containers. + resources: {} + # requests: + # cpu: "100m" +@@ -839,8 +847,8 @@ resources: {} + # cpu: "300m" + # memory: "150Mi" + +-# This example pod anti-affinity forces the scheduler to put traefik pods +-# on nodes where no other traefik pods are scheduled. ++# -- This example pod anti-affinity forces the scheduler to put traefik pods ++# -- on nodes where no other traefik pods are scheduled. + # It should be used when hostNetwork: true to prevent port conflicts + affinity: {} + # podAntiAffinity: +@@ -851,11 +859,15 @@ affinity: {} + # app.kubernetes.io/instance: '{{ .Release.Name }}-{{ .Release.Namespace }}' + # topologyKey: kubernetes.io/hostname + ++# -- nodeSelector is the simplest recommended form of node selection constraint. + nodeSelector: {} ++# -- Tolerations allow the scheduler to schedule pods with matching taints. + tolerations: [] ++# -- You can use topology spread constraints to control ++# how Pods are spread across your cluster among failure-domains. + topologySpreadConstraints: [] +-# # This example topologySpreadConstraints forces the scheduler to put traefik pods +-# # on nodes where no other traefik pods are scheduled. ++# This example topologySpreadConstraints forces the scheduler to put traefik pods ++# on nodes where no other traefik pods are scheduled. + # - labelSelector: + # matchLabels: + # app: '{{ template "traefik.name" . }}' +@@ -863,29 +875,33 @@ topologySpreadConstraints: [] + # topologyKey: kubernetes.io/hostname + # whenUnsatisfiable: DoNotSchedule + +-# Pods can have priority. +-# Priority indicates the importance of a Pod relative to other Pods. ++# -- Pods can have priority. ++# -- Priority indicates the importance of a Pod relative to other Pods. + priorityClassName: "" + +-# Set the container security context +-# To run the container with ports below 1024 this will need to be adjust to run as root ++# -- Set the container security context ++# -- To run the container with ports below 1024 this will need to be adjust to run as root + securityContext: + capabilities: + drop: [ALL] + readOnlyRootFilesystem: true + + podSecurityContext: +-# # /!\ When setting fsGroup, Kubernetes will recursively changes ownership and +-# # permissions for the contents of each volume to match the fsGroup. This can +-# # be an issue when storing sensitive content like TLS Certificates /!\ +-# fsGroup: 65532 ++ # /!\ When setting fsGroup, Kubernetes will recursively changes ownership and ++ # permissions for the contents of each volume to match the fsGroup. This can ++ # be an issue when storing sensitive content like TLS Certificates /!\ ++ # fsGroup: 65532 ++ # -- Specifies the policy for changing ownership and permissions of volume contents to match the fsGroup. + fsGroupChangePolicy: "OnRootMismatch" ++ # -- The ID of the group for all containers in the pod to run as. + runAsGroup: 65532 ++ # -- Specifies whether the containers should run as a non-root user. + runAsNonRoot: true ++ # -- The ID of the user for all containers in the pod to run as. + runAsUser: 65532 + + # +-# Extra objects to deploy (value evaluated as a template) ++# -- Extra objects to deploy (value evaluated as a template) + # + # In some cases, it can avoid the need for additional, extended or adhoc deployments. + # See #595 for more details and traefik/tests/values/extra.yaml for example. +@@ -895,5 +911,5 @@ extraObjects: [] + # It will not affect optional CRDs such as `ServiceMonitor` and `PrometheusRules` + # namespaceOverride: traefik + # +-## This will override the default app.kubernetes.io/instance label for all Objects. ++## -- This will override the default app.kubernetes.io/instance label for all Objects. + # instanceLabelOverride: traefik +``` + +## 23.0.1 ![AppVersion: v2.10.1](https://img.shields.io/static/v1?label=AppVersion&message=v2.10.1&color=success&logo=) ![Kubernetes: >=1.16.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.16.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2023-04-28 + +* fix: ⬆️ Upgrade traefik Docker tag to v2.10.1 + + +## 23.0.0 ![AppVersion: v2.10.0](https://img.shields.io/static/v1?label=AppVersion&message=v2.10.0&color=success&logo=) ![Kubernetes: >=1.16.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.16.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2023-04-26 + +* BREAKING CHANGE: Traefik 2.10 comes with CRDs update on API Group + + +## 22.3.0 ![AppVersion: v2.10.0](https://img.shields.io/static/v1?label=AppVersion&message=v2.10.0&color=success&logo=) ![Kubernetes: >=1.16.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.16.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2023-04-25 + +* ⬆️ Upgrade traefik Docker tag to v2.10.0 +* fix: 🐛 update rbac for both traefik.io and containo.us apigroups (#836) +* breaking: 💥 update CRDs needed for Traefik v2.10 + + +## 22.2.0 ![AppVersion: v2.9.10](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.10&color=success&logo=) ![Kubernetes: >=1.16.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.16.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2023-04-24 + +* test: 👷 Update unit tests tooling +* fix: 🐛 annotations leaking between aliased subcharts +* fix: indentation on `TLSOption` +* feat: override container port +* feat: allow to set dnsConfig on pod template +* chore: 🔧 new release +* added targetPort support + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 9ece303..71273cc 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -82,6 +82,16 @@ deployment: + shareProcessNamespace: false + # Custom pod DNS policy. Apply if `hostNetwork: true` + # dnsPolicy: ClusterFirstWithHostNet ++ dnsConfig: {} ++ # nameservers: ++ # - 192.0.2.1 # this is an example ++ # searches: ++ # - ns1.svc.cluster-domain.example ++ # - my.dns.search.suffix ++ # options: ++ # - name: ndots ++ # value: "2" ++ # - name: edns0 + # Additional imagePullSecrets + imagePullSecrets: [] + # - name: myRegistryKeySecretName +@@ -561,8 +571,11 @@ ports: + # asDefault: true + port: 8000 + # hostPort: 8000 ++ # containerPort: 8000 + expose: true + exposedPort: 80 ++ ## Different target traefik port on the cluster, useful for IP type LB ++ # targetPort: 80 + # The port protocol (TCP/UDP) + protocol: TCP + # Use nodeport if set. This is useful if you have configured Traefik in a +@@ -587,8 +600,11 @@ ports: + # asDefault: true + port: 8443 + # hostPort: 8443 ++ # containerPort: 8443 + expose: true + exposedPort: 443 ++ ## Different target traefik port on the cluster, useful for IP type LB ++ # targetPort: 80 + ## The port protocol (TCP/UDP) + protocol: TCP + # nodePort: 32443 +``` + +## 22.1.0 ![AppVersion: v2.9.10](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.10&color=success&logo=) ![Kubernetes: >=1.16.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.16.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2023-04-07 + +* ⬆️ Upgrade traefik Docker tag to v2.9.10 +* feat: add additional labels to tlsoption + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 4762b77..9ece303 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -654,12 +654,15 @@ ports: + + # TLS Options are created as TLSOption CRDs + # https://doc.traefik.io/traefik/https/tls/#tls-options ++# When using `labelSelector`, you'll need to set labels on tlsOption accordingly. + # Example: + # tlsOptions: + # default: ++# labels: {} + # sniStrict: true + # preferServerCipherSuites: true +-# foobar: ++# customOptions: ++# labels: {} + # curvePreferences: + # - CurveP521 + # - CurveP384 +``` + +## 22.0.0 ![AppVersion: v2.9.9](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.9&color=success&logo=) ![Kubernetes: >=1.16.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.16.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2023-03-29 + +* BREAKING CHANGE: `image.repository` introduction may break during the upgrade. See PR #802. + + +## 21.2.1 ![AppVersion: v2.9.9](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.9&color=success&logo=) ![Kubernetes: >=1.16.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.16.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2023-03-28 + +* 🎨 Introduce `image.registry` and add explicit default (it may impact custom `image.repository`) +* ⬆️ Upgrade traefik Docker tag to v2.9.9 +* :memo: Clarify the need of an initContainer when enabling persistence for TLS Certificates + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index cadc7a6..4762b77 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -1,5 +1,6 @@ + # Default values for Traefik + image: ++ registry: docker.io + repository: traefik + # defaults to appVersion + tag: "" +@@ -66,10 +67,14 @@ deployment: + # Additional initContainers (e.g. for setting file permission as shown below) + initContainers: [] + # The "volume-permissions" init container is required if you run into permission issues. +- # Related issue: https://github.com/traefik/traefik/issues/6825 ++ # Related issue: https://github.com/traefik/traefik-helm-chart/issues/396 + # - name: volume-permissions +- # image: busybox:1.35 +- # command: ["sh", "-c", "touch /data/acme.json && chmod -Rv 600 /data/* && chown 65532:65532 /data/acme.json"] ++ # image: busybox:latest ++ # command: ["sh", "-c", "touch /data/acme.json; chmod -v 600 /data/acme.json"] ++ # securityContext: ++ # runAsNonRoot: true ++ # runAsGroup: 65532 ++ # runAsUser: 65532 + # volumeMounts: + # - name: data + # mountPath: /data +@@ -849,13 +854,17 @@ securityContext: + capabilities: + drop: [ALL] + readOnlyRootFilesystem: true ++ ++podSecurityContext: ++# # /!\ When setting fsGroup, Kubernetes will recursively changes ownership and ++# # permissions for the contents of each volume to match the fsGroup. This can ++# # be an issue when storing sensitive content like TLS Certificates /!\ ++# fsGroup: 65532 ++ fsGroupChangePolicy: "OnRootMismatch" + runAsGroup: 65532 + runAsNonRoot: true + runAsUser: 65532 + +-podSecurityContext: +- fsGroup: 65532 +- + # + # Extra objects to deploy (value evaluated as a template) + # +``` + +## 21.2.0 ![AppVersion: v2.9.8](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.8&color=success&logo=) ![Kubernetes: >=1.16.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.16.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2023-03-08 + +* 🚨 Fail when enabling PSP on Kubernetes v1.25+ (#801) +* ⬆️ Upgrade traefik Docker tag to v2.9.8 +* Separate UDP hostPort for HTTP/3 +* :sparkles: release 21.2.0 (#805) + + +## 21.1.0 ![AppVersion: v2.9.7](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.7&color=success&logo=) ![Kubernetes: >=1.16.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.16.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2023-02-15 + +* ⬆️ Upgrade traefik Docker tag to v2.9.7 +* ✨ release 21.1.0 +* fix: traefik image name for renovate +* feat: Add volumeName to PersistentVolumeClaim (#792) +* Allow setting TLS options on dashboard IngressRoute + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 780b04b..cadc7a6 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -142,6 +142,8 @@ ingressRoute: + entryPoints: ["traefik"] + # Additional ingressRoute middlewares (e.g. for authentication) + middlewares: [] ++ # TLS options (e.g. secret containing certificate) ++ tls: {} + + # Customize updateStrategy of traefik pods + updateStrategy: +@@ -750,6 +752,7 @@ persistence: + accessMode: ReadWriteOnce + size: 128Mi + # storageClass: "" ++ # volumeName: "" + path: /data + annotations: {} + # subPath: "" # only mount a subpath of the Volume into the pod +``` + +## 21.0.0 ![AppVersion: v2.9.6](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.6&color=success&logo=) ![Kubernetes: >=1.16.0-0](https://img.shields.io/static/v1?label=Kubernetes&message=%3E%3D1.16.0-0&color=informational&logo=kubernetes) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2023-02-10 + +* 🙈 Add a setting disable API check on Prometheus Operator (#769) +* 📝 Improve documentation on entrypoint options +* 💥 New release with BREAKING changes (#786) +* ✨ Chart.yaml - add kubeVersion: ">=1.16.0-0" +* fix: allowExternalNameServices for kubernetes ingress when hub enabled (#772) +* fix(service-metrics): invert prometheus svc & fullname length checking +* Configure Renovate (#783) +* :necktie: Improve labels settings behavior on metrics providers (#774) +* :bug: Disabling dashboard ingressroute should delete it (#785) +* :boom: Rename image.name => image.repository (#784) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 42a27f9..780b04b 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -1,6 +1,6 @@ + # Default values for Traefik + image: +- name: traefik ++ repository: traefik + # defaults to appVersion + tag: "" + pullPolicy: IfNotPresent +@@ -396,6 +396,8 @@ metrics: + # enabled: false + # labels: {} + # annotations: {} ++ ## When set to true, it won't check if Prometheus Operator CRDs are deployed ++ # disableAPICheck: false + # serviceMonitor: + # metricRelabelings: [] + # - sourceLabels: [__name__] +@@ -580,7 +582,7 @@ ports: + # hostPort: 8443 + expose: true + exposedPort: 443 +- # The port protocol (TCP/UDP) ++ ## The port protocol (TCP/UDP) + protocol: TCP + # nodePort: 32443 + # +@@ -594,6 +596,16 @@ ports: + enabled: false + # advertisedPort: 4443 + # ++ ## Trust forwarded headers information (X-Forwarded-*). ++ #forwardedHeaders: ++ # trustedIPs: [] ++ # insecure: false ++ # ++ ## Enable the Proxy Protocol header parsing for the entry point ++ #proxyProtocol: ++ # trustedIPs: [] ++ # insecure: false ++ # + ## Set TLS at the entrypoint + ## https://doc.traefik.io/traefik/routing/entrypoints/#tls + tls: +@@ -607,16 +619,6 @@ ports: + # - foo.example.com + # - bar.example.com + # +- # Trust forwarded headers information (X-Forwarded-*). +- # forwardedHeaders: +- # trustedIPs: [] +- # insecure: false +- # +- # Enable the Proxy Protocol header parsing for the entry point +- # proxyProtocol: +- # trustedIPs: [] +- # insecure: false +- # + # One can apply Middlewares on an entrypoint + # https://doc.traefik.io/traefik/middlewares/overview/ + # https://doc.traefik.io/traefik/routing/entrypoints/#middlewares +``` + +## 20.8.0 ![AppVersion: v2.9.6](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.6&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-12-09 + +* ✨ update chart to version 20.8.0 +* ✨ add support for default entrypoints +* ✨ add support for OpenTelemetry and Traefik v3 + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index b77539d..42a27f9 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -107,6 +107,8 @@ ingressClass: + + # Enable experimental features + experimental: ++ v3: ++ enabled: false + plugins: + enabled: false + kubernetesGateway: +@@ -347,7 +349,43 @@ metrics: + # # addRoutersLabels: true + # ## Enable metrics on services. Default=true + # # addServicesLabels: false +- ++# openTelemetry: ++# ## Address of the OpenTelemetry Collector to send metrics to. ++# address: "localhost:4318" ++# ## Enable metrics on entry points. ++# addEntryPointsLabels: true ++# ## Enable metrics on routers. ++# addRoutersLabels: true ++# ## Enable metrics on services. ++# addServicesLabels: true ++# ## Explicit boundaries for Histogram data points. ++# explicitBoundaries: ++# - "0.1" ++# - "0.3" ++# - "1.2" ++# - "5.0" ++# ## Additional headers sent with metrics by the reporter to the OpenTelemetry Collector. ++# headers: ++# foo: bar ++# test: test ++# ## Allows reporter to send metrics to the OpenTelemetry Collector without using a secured protocol. ++# insecure: true ++# ## Interval at which metrics are sent to the OpenTelemetry Collector. ++# pushInterval: 10s ++# ## Allows to override the default URL path used for sending metrics. This option has no effect when using gRPC transport. ++# path: /foo/v1/traces ++# ## Defines the TLS configuration used by the reporter to send metrics to the OpenTelemetry Collector. ++# tls: ++# ## The path to the certificate authority, it defaults to the system bundle. ++# ca: path/to/ca.crt ++# ## The path to the public certificate. When using this option, setting the key option is required. ++# cert: path/to/foo.cert ++# ## The path to the private key. When using this option, setting the cert option is required. ++# key: path/to/key.key ++# ## If set to true, the TLS connection accepts any certificate presented by the server regardless of the hostnames it covers. ++# insecureSkipVerify: true ++# ## This instructs the reporter to send metrics to the OpenTelemetry Collector using gRPC. ++# grpc: true + + ## + ## enable optional CRDs for Prometheus Operator +@@ -510,6 +548,8 @@ ports: + # The port protocol (TCP/UDP) + protocol: TCP + web: ++ ## Enable this entrypoint as a default entrypoint. When a service doesn't explicity set an entrypoint it will only use this entrypoint. ++ # asDefault: true + port: 8000 + # hostPort: 8000 + expose: true +@@ -534,6 +574,8 @@ ports: + # trustedIPs: [] + # insecure: false + websecure: ++ ## Enable this entrypoint as a default entrypoint. When a service doesn't explicity set an entrypoint it will only use this entrypoint. ++ # asDefault: true + port: 8443 + # hostPort: 8443 + expose: true +``` + +## 20.7.0 ![AppVersion: v2.9.6](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.6&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-12-08 + +* 🐛 Don't fail when prometheus is disabled (#756) +* ⬆️ Update default Traefik release to v2.9.6 (#758) +* ✨ support for Gateway annotations +* add keywords [networking], for artifacthub category quering +* :bug: Fix typo on bufferingSize for access logs (#753) +* :adhesive_bandage: Add quotes for artifacthub changelog parsing (#748) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 4f2fb2a..b77539d 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -120,6 +120,9 @@ experimental: + # By default, Gateway would be created to the Namespace you are deploying Traefik to. + # You may create that Gateway in another namespace, setting its name below: + # namespace: default ++ # Additional gateway annotations (e.g. for cert-manager.io/issuer) ++ # annotations: ++ # cert-manager.io/issuer: letsencrypt + + # Create an IngressRoute for the dashboard + ingressRoute: +@@ -219,7 +222,8 @@ logs: + # By default, the logs use a text format (common), but you can + # also ask for the json format in the format option + # format: json +- # By default, the level is set to ERROR. Alternative logging levels are DEBUG, PANIC, FATAL, ERROR, WARN, and INFO. ++ # By default, the level is set to ERROR. ++ # Alternative logging levels are DEBUG, PANIC, FATAL, ERROR, WARN, and INFO. + level: ERROR + access: + # To enable access logs +``` + +## 20.6.0 ![AppVersion: v2.9.5](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.5&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-11-30 + +* 🔍️ Add filePath support on access logs (#747) +* :memo: Improve documentation on using PVC with TLS certificates +* :bug: Add missing scheme in help on Traefik Hub integration (#746) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 15f1682..4f2fb2a 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -211,10 +211,10 @@ additionalVolumeMounts: [] + # - name: traefik-logs + # mountPath: /var/log/traefik + +-# Logs +-# https://docs.traefik.io/observability/logs/ ++## Logs ++## https://docs.traefik.io/observability/logs/ + logs: +- # Traefik logs concern everything that happens to Traefik itself (startup, configuration, events, shutdown, and so on). ++ ## Traefik logs concern everything that happens to Traefik itself (startup, configuration, events, shutdown, and so on). + general: + # By default, the logs use a text format (common), but you can + # also ask for the json format in the format option +@@ -224,31 +224,32 @@ logs: + access: + # To enable access logs + enabled: false +- # By default, logs are written using the Common Log Format (CLF). +- # To write logs in JSON, use json in the format option. +- # If the given format is unsupported, the default (CLF) is used instead. ++ ## By default, logs are written using the Common Log Format (CLF) on stdout. ++ ## To write logs in JSON, use json in the format option. ++ ## If the given format is unsupported, the default (CLF) is used instead. + # format: json +- # To write the logs in an asynchronous fashion, specify a bufferingSize option. +- # This option represents the number of log lines Traefik will keep in memory before writing +- # them to the selected output. In some cases, this option can greatly help performances. ++ # filePath: "/var/log/traefik/access.log ++ ## To write the logs in an asynchronous fashion, specify a bufferingSize option. ++ ## This option represents the number of log lines Traefik will keep in memory before writing ++ ## them to the selected output. In some cases, this option can greatly help performances. + # bufferingSize: 100 +- # Filtering https://docs.traefik.io/observability/access-logs/#filtering ++ ## Filtering https://docs.traefik.io/observability/access-logs/#filtering + filters: {} + # statuscodes: "200,300-302" + # retryattempts: true + # minduration: 10ms +- # Fields +- # https://docs.traefik.io/observability/access-logs/#limiting-the-fieldsincluding-headers ++ ## Fields ++ ## https://docs.traefik.io/observability/access-logs/#limiting-the-fieldsincluding-headers + fields: + general: + defaultmode: keep + names: {} +- # Examples: ++ ## Examples: + # ClientUsername: drop + headers: + defaultmode: drop + names: {} +- # Examples: ++ ## Examples: + # User-Agent: redact + # Authorization: drop + # Content-Type: keep +@@ -693,10 +694,7 @@ autoscaling: + + # Enable persistence using Persistent Volume Claims + # ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ +-# After the pvc has been mounted, add the configs into traefik by using the `additionalArguments` list below, eg: +-# additionalArguments: +-# - "--certificatesresolvers.le.acme.storage=/data/acme.json" +-# It will persist TLS certificates. ++# It can be used to store TLS certificates, see `storage` in certResolvers + persistence: + enabled: false + name: data +@@ -726,7 +724,7 @@ certResolvers: {} + # tlsChallenge: true + # httpChallenge: + # entryPoint: "web" +-# # match the path to persistence ++# # It has to match the path with a persistent volume + # storage: /data/acme.json + + # If hostNetwork is true, runs traefik in the host network namespace +``` + +## 20.5.3 ![AppVersion: v2.9.5](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.5&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-11-25 + +* 🐛 Fix template issue with obsolete helm version + add helm version requirement (#743) + + +## 20.5.2 ![AppVersion: v2.9.5](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.5&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-11-24 + +* ⬆️Update Traefik to v2.9.5 (#740) + + +## 20.5.1 ![AppVersion: v2.9.4](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-11-23 + +* 🐛 Fix namespaceSelector on ServiceMonitor (#737) + + +## 20.5.0 ![AppVersion: v2.9.4](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-11-23 + +* 🚀 Add complete support on metrics options (#735) +* 🐛 make tests use fixed version + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index e49d02d..15f1682 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -12,7 +12,7 @@ hub: + ## Enabling Hub will: + # * enable Traefik Hub integration on Traefik + # * add `traefikhub-tunl` endpoint +- # * enable addRoutersLabels on prometheus metrics ++ # * enable Prometheus metrics with addRoutersLabels + # * enable allowExternalNameServices on KubernetesIngress provider + # * enable allowCrossNamespace on KubernetesCRD provider + # * add an internal (ClusterIP) Service, dedicated for Traefik Hub +@@ -254,16 +254,96 @@ logs: + # Content-Type: keep + + metrics: +- # datadog: +- # address: 127.0.0.1:8125 +- # influxdb: +- # address: localhost:8089 +- # protocol: udp ++ ## Prometheus is enabled by default. ++ ## It can be disabled by setting "prometheus: null" + prometheus: ++ ## Entry point used to expose metrics. + entryPoint: metrics +- # addRoutersLabels: true +- # statsd: +- # address: localhost:8125 ++ ## Enable metrics on entry points. Default=true ++ # addEntryPointsLabels: false ++ ## Enable metrics on routers. Default=false ++ # addRoutersLabels: true ++ ## Enable metrics on services. Default=true ++ # addServicesLabels: false ++ ## Buckets for latency metrics. Default="0.1,0.3,1.2,5.0" ++ # buckets: "0.5,1.0,2.5" ++ ## When manualRouting is true, it disables the default internal router in ++ ## order to allow creating a custom router for prometheus@internal service. ++ # manualRouting: true ++# datadog: ++# ## Address instructs exporter to send metrics to datadog-agent at this address. ++# address: "127.0.0.1:8125" ++# ## The interval used by the exporter to push metrics to datadog-agent. Default=10s ++# # pushInterval: 30s ++# ## The prefix to use for metrics collection. Default="traefik" ++# # prefix: traefik ++# ## Enable metrics on entry points. Default=true ++# # addEntryPointsLabels: false ++# ## Enable metrics on routers. Default=false ++# # addRoutersLabels: true ++# ## Enable metrics on services. Default=true ++# # addServicesLabels: false ++# influxdb: ++# ## Address instructs exporter to send metrics to influxdb at this address. ++# address: localhost:8089 ++# ## InfluxDB's address protocol (udp or http). Default="udp" ++# protocol: udp ++# ## InfluxDB database used when protocol is http. Default="" ++# # database: "" ++# ## InfluxDB retention policy used when protocol is http. Default="" ++# # retentionPolicy: "" ++# ## InfluxDB username (only with http). Default="" ++# # username: "" ++# ## InfluxDB password (only with http). Default="" ++# # password: "" ++# ## The interval used by the exporter to push metrics to influxdb. Default=10s ++# # pushInterval: 30s ++# ## Additional labels (influxdb tags) on all metrics. ++# # additionalLabels: ++# # env: production ++# # foo: bar ++# ## Enable metrics on entry points. Default=true ++# # addEntryPointsLabels: false ++# ## Enable metrics on routers. Default=false ++# # addRoutersLabels: true ++# ## Enable metrics on services. Default=true ++# # addServicesLabels: false ++# influxdb2: ++# ## Address instructs exporter to send metrics to influxdb v2 at this address. ++# address: localhost:8086 ++# ## Token with which to connect to InfluxDB v2. ++# token: xxx ++# ## Organisation where metrics will be stored. ++# org: "" ++# ## Bucket where metrics will be stored. ++# bucket: "" ++# ## The interval used by the exporter to push metrics to influxdb. Default=10s ++# # pushInterval: 30s ++# ## Additional labels (influxdb tags) on all metrics. ++# # additionalLabels: ++# # env: production ++# # foo: bar ++# ## Enable metrics on entry points. Default=true ++# # addEntryPointsLabels: false ++# ## Enable metrics on routers. Default=false ++# # addRoutersLabels: true ++# ## Enable metrics on services. Default=true ++# # addServicesLabels: false ++# statsd: ++# ## Address instructs exporter to send metrics to statsd at this address. ++# address: localhost:8125 ++# ## The interval used by the exporter to push metrics to influxdb. Default=10s ++# # pushInterval: 30s ++# ## The prefix to use for metrics collection. Default="traefik" ++# # prefix: traefik ++# ## Enable metrics on entry points. Default=true ++# # addEntryPointsLabels: false ++# ## Enable metrics on routers. Default=false ++# # addRoutersLabels: true ++# ## Enable metrics on services. Default=true ++# # addServicesLabels: false ++ ++ + ## + ## enable optional CRDs for Prometheus Operator + ## +``` + +## 20.4.1 ![AppVersion: v2.9.4](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-11-21 + +* 🐛 fix namespace references to support namespaceOverride + + +## 20.4.0 ![AppVersion: v2.9.4](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-11-21 + +* Add (optional) dedicated metrics service (#727) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index ca15f6a..e49d02d 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -267,6 +267,12 @@ metrics: + ## + ## enable optional CRDs for Prometheus Operator + ## ++ ## Create a dedicated metrics service for use with ServiceMonitor ++ ## When hub.enabled is set to true, it's not needed: it will use hub service. ++ # service: ++ # enabled: false ++ # labels: {} ++ # annotations: {} + # serviceMonitor: + # metricRelabelings: [] + # - sourceLabels: [__name__] +``` + +## 20.3.1 ![AppVersion: v2.9.4](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-11-21 + +* 🐛 Fix namespace override which was missing on `ServiceAccount` (#731) + + +## 20.3.0 ![AppVersion: v2.9.4](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-11-17 + +* Add overwrite option for instance label value (#725) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index c7f84a7..ca15f6a 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -731,3 +731,6 @@ extraObjects: [] + # This will override the default Release Namespace for Helm. + # It will not affect optional CRDs such as `ServiceMonitor` and `PrometheusRules` + # namespaceOverride: traefik ++# ++## This will override the default app.kubernetes.io/instance label for all Objects. ++# instanceLabelOverride: traefik +``` + +## 20.2.1 ![AppVersion: v2.9.4](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-11-17 + +* 🙈 do not namespace ingress class (#723) +* ✨ copy LICENSE and README.md on release + + +## 20.2.0 ![AppVersion: v2.9.4](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-11-15 + +* ✨ add support for namespace overrides (#718) +* Document recent changes in the README (#717) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 97a1b71..c7f84a7 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -725,5 +725,9 @@ podSecurityContext: + # Extra objects to deploy (value evaluated as a template) + # + # In some cases, it can avoid the need for additional, extended or adhoc deployments. +-# See #595 for more details and traefik/tests/extra.yaml for example. ++# See #595 for more details and traefik/tests/values/extra.yaml for example. + extraObjects: [] ++ ++# This will override the default Release Namespace for Helm. ++# It will not affect optional CRDs such as `ServiceMonitor` and `PrometheusRules` ++# namespaceOverride: traefik +``` + +## 20.1.1 ![AppVersion: v2.9.4](https://img.shields.io/static/v1?label=AppVersion&message=v2.9.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-11-10 + +* fix: use consistent appVersion with Traefik Proxy + + +## 20.1.0 ![AppVersion: 2.9.4](https://img.shields.io/static/v1?label=AppVersion&message=2.9.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-11-09 + +* 🔧 Adds more settings for dashboard ingressRoute (#710) +* 🐛 fix chart releases + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 2ec3736..97a1b71 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -129,10 +129,14 @@ ingressRoute: + annotations: {} + # Additional ingressRoute labels (e.g. for filtering IngressRoute by custom labels) + labels: {} ++ # The router match rule used for the dashboard ingressRoute ++ matchRule: PathPrefix(`/dashboard`) || PathPrefix(`/api`) + # Specify the allowed entrypoints to use for the dashboard ingress route, (e.g. traefik, web, websecure). + # By default, it's using traefik entrypoint, which is not exposed. + # /!\ Do not expose your dashboard without any protection over the internet /!\ + entryPoints: ["traefik"] ++ # Additional ingressRoute middlewares (e.g. for authentication) ++ middlewares: [] + + # Customize updateStrategy of traefik pods + updateStrategy: +``` + +## 20.0.0 ![AppVersion: 2.9.4](https://img.shields.io/static/v1?label=AppVersion&message=2.9.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-11-08 + +* 🐛 remove old deployment workflow +* ✨ migrate to centralised helm repository +* Allow updateStrategy to be configurable + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 413aa88..2ec3736 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -134,9 +134,12 @@ ingressRoute: + # /!\ Do not expose your dashboard without any protection over the internet /!\ + entryPoints: ["traefik"] + +-rollingUpdate: +- maxUnavailable: 0 +- maxSurge: 1 ++# Customize updateStrategy of traefik pods ++updateStrategy: ++ type: RollingUpdate ++ rollingUpdate: ++ maxUnavailable: 0 ++ maxSurge: 1 + + # Customize liveness and readiness probe values. + readinessProbe: +``` + +## 19.0.4 ![AppVersion: 2.9.4](https://img.shields.io/static/v1?label=AppVersion&message=2.9.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-11-08 + +* 🔧 Adds more settings & rename (wrong) scrapeInterval to (valid) interval on ServiceMonitor (#703) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index b24c1cb..413aa88 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -261,10 +261,6 @@ metrics: + ## enable optional CRDs for Prometheus Operator + ## + # serviceMonitor: +- # additionalLabels: +- # foo: bar +- # namespace: "another-namespace" +- # namespaceSelector: {} + # metricRelabelings: [] + # - sourceLabels: [__name__] + # separator: ; +@@ -279,9 +275,17 @@ metrics: + # replacement: $1 + # action: replace + # jobLabel: traefik +- # scrapeInterval: 30s +- # scrapeTimeout: 5s ++ # interval: 30s + # honorLabels: true ++ # # (Optional) ++ # # scrapeTimeout: 5s ++ # # honorTimestamps: true ++ # # enableHttp2: true ++ # # followRedirects: true ++ # # additionalLabels: ++ # # foo: bar ++ # # namespace: "another-namespace" ++ # # namespaceSelector: {} + # prometheusRule: + # additionalLabels: {} + # namespace: "another-namespace" +``` + +## 19.0.3 ![AppVersion: 2.9.4](https://img.shields.io/static/v1?label=AppVersion&message=2.9.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-11-03 + +* 🎨 Don't require exposed Ports when enabling Hub (#700) + + +## 19.0.2 ![AppVersion: 2.9.4](https://img.shields.io/static/v1?label=AppVersion&message=2.9.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-11-03 + +* :speech_balloon: Support volume secrets with '.' in name (#695) + + +## 19.0.1 ![AppVersion: 2.9.4](https://img.shields.io/static/v1?label=AppVersion&message=2.9.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-11-03 + +* 🐛 Fix IngressClass install on EKS (#699) + + +## 19.0.0 ![AppVersion: 2.9.4](https://img.shields.io/static/v1?label=AppVersion&message=2.9.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-11-02 + +* ✨ Provides Default IngressClass for Traefik by default (#693) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 69190f1..b24c1cb 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -100,11 +100,10 @@ podDisruptionBudget: + # minAvailable: 0 + # minAvailable: 25% + +-# Use ingressClass. Ignored if Traefik version < 2.3 / kubernetes < 1.18.x ++# Create a default IngressClass for Traefik + ingressClass: +- # true is not unit-testable yet, pending https://github.com/rancher/helm-unittest/pull/12 +- enabled: false +- isDefaultClass: false ++ enabled: true ++ isDefaultClass: true + + # Enable experimental features + experimental: +``` + +## 18.3.0 ![AppVersion: 2.9.4](https://img.shields.io/static/v1?label=AppVersion&message=2.9.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-31 + +* ⬆️ Update Traefik appVersion to 2.9.4 (#696) + + +## 18.2.0 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-31 + +* 🚩 Add an optional "internal" service (#683) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 8033a87..69190f1 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -416,7 +416,7 @@ ports: + # The port protocol (TCP/UDP) + protocol: TCP + # Use nodeport if set. This is useful if you have configured Traefik in a +- # LoadBalancer ++ # LoadBalancer. + # nodePort: 32080 + # Port Redirections + # Added in 2.2, you can make permanent redirects via entrypoints. +@@ -549,13 +549,24 @@ service: + # - 172.16.0.0/16 + externalIPs: [] + # - 1.2.3.4 +- # One of SingleStack, PreferDualStack, or RequireDualStack. ++ ## One of SingleStack, PreferDualStack, or RequireDualStack. + # ipFamilyPolicy: SingleStack +- # List of IP families (e.g. IPv4 and/or IPv6). +- # ref: https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services ++ ## List of IP families (e.g. IPv4 and/or IPv6). ++ ## ref: https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services + # ipFamilies: + # - IPv4 + # - IPv6 ++ ## ++ ## An additionnal and optional internal Service. ++ ## Same parameters as external Service ++ # internal: ++ # type: ClusterIP ++ # # labels: {} ++ # # annotations: {} ++ # # spec: {} ++ # # loadBalancerSourceRanges: [] ++ # # externalIPs: [] ++ # # ipFamilies: [ "IPv4","IPv6" ] + + ## Create HorizontalPodAutoscaler object. + ## +``` + +## 18.1.0 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-27 + +* 🚀 Add native support for Traefik Hub (#676) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index acce704..8033a87 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -5,6 +5,27 @@ image: + tag: "" + pullPolicy: IfNotPresent + ++# ++# Configure integration with Traefik Hub ++# ++hub: ++ ## Enabling Hub will: ++ # * enable Traefik Hub integration on Traefik ++ # * add `traefikhub-tunl` endpoint ++ # * enable addRoutersLabels on prometheus metrics ++ # * enable allowExternalNameServices on KubernetesIngress provider ++ # * enable allowCrossNamespace on KubernetesCRD provider ++ # * add an internal (ClusterIP) Service, dedicated for Traefik Hub ++ enabled: false ++ ## Default port can be changed ++ # tunnelPort: 9901 ++ ## TLS is optional. Insecure is mutually exclusive with any other options ++ # tls: ++ # insecure: false ++ # ca: "/path/to/ca.pem" ++ # cert: "/path/to/cert.pem" ++ # key: "/path/to/key.pem" ++ + # + # Configure the deployment + # +@@ -505,6 +526,8 @@ tlsStore: {} + # from. + service: + enabled: true ++ ## Single service is using `MixedProtocolLBService` feature gate. ++ ## When set to false, it will create two Service, one for TCP and one for UDP. + single: true + type: LoadBalancer + # Additional annotations applied to both TCP and UDP services (e.g. for cloud provider specific config) +``` + +## 18.0.0 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-26 + +* Refactor http3 and merge TCP with UDP ports into a single service (#656) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 807bd09..acce704 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -87,8 +87,6 @@ ingressClass: + + # Enable experimental features + experimental: +- http3: +- enabled: false + plugins: + enabled: false + kubernetesGateway: +@@ -421,12 +419,19 @@ ports: + # The port protocol (TCP/UDP) + protocol: TCP + # nodePort: 32443 +- # Enable HTTP/3. +- # Requires enabling experimental http3 feature and tls. +- # Note that you cannot have a UDP entrypoint with the same port. +- # http3: true +- # Set TLS at the entrypoint +- # https://doc.traefik.io/traefik/routing/entrypoints/#tls ++ # ++ ## Enable HTTP/3 on the entrypoint ++ ## Enabling it will also enable http3 experimental feature ++ ## https://doc.traefik.io/traefik/routing/entrypoints/#http3 ++ ## There are known limitations when trying to listen on same ports for ++ ## TCP & UDP (Http3). There is a workaround in this chart using dual Service. ++ ## https://github.com/kubernetes/kubernetes/issues/47249#issuecomment-587960741 ++ http3: ++ enabled: false ++ # advertisedPort: 4443 ++ # ++ ## Set TLS at the entrypoint ++ ## https://doc.traefik.io/traefik/routing/entrypoints/#tls + tls: + enabled: true + # this is the name of a TLSOption definition +@@ -500,6 +505,7 @@ tlsStore: {} + # from. + service: + enabled: true ++ single: true + type: LoadBalancer + # Additional annotations applied to both TCP and UDP services (e.g. for cloud provider specific config) + annotations: {} +``` + +## 17.0.5 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-21 + +* 📝 Add annotations changelog for artifacthub.io & update Maintainers + + +## 17.0.4 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-21 + +* :art: Add helper function for label selector + + +## 17.0.3 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-20 + +* 🐛 fix changing label selectors + + +## 17.0.2 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-20 + +* fix: setting ports.web.proxyProtocol.insecure=true + + +## 17.0.1 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-20 + +* :bug: Unify all labels selector with traefik chart labels (#681) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 6a90bc6..807bd09 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -639,7 +639,7 @@ affinity: {} + # - labelSelector: + # matchLabels: + # app.kubernetes.io/name: '{{ template "traefik.name" . }}' +-# app.kubernetes.io/instance: '{{ .Release.Name }}' ++# app.kubernetes.io/instance: '{{ .Release.Name }}-{{ .Release.Namespace }}' + # topologyKey: kubernetes.io/hostname + + nodeSelector: {} +``` + +## 17.0.0 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-20 + +* :bug: Fix `ClusterRole`, `ClusterRoleBinding` names and `app.kubernetes.io/instance` label (#662) + + +## 16.2.0 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-20 + +* Add forwardedHeaders and proxyProtocol config (#673) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 9b5afc4..6a90bc6 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -403,6 +403,16 @@ ports: + # Added in 2.2, you can make permanent redirects via entrypoints. + # https://docs.traefik.io/routing/entrypoints/#redirection + # redirectTo: websecure ++ # ++ # Trust forwarded headers information (X-Forwarded-*). ++ # forwardedHeaders: ++ # trustedIPs: [] ++ # insecure: false ++ # ++ # Enable the Proxy Protocol header parsing for the entry point ++ # proxyProtocol: ++ # trustedIPs: [] ++ # insecure: false + websecure: + port: 8443 + # hostPort: 8443 +@@ -428,6 +438,16 @@ ports: + # - foo.example.com + # - bar.example.com + # ++ # Trust forwarded headers information (X-Forwarded-*). ++ # forwardedHeaders: ++ # trustedIPs: [] ++ # insecure: false ++ # ++ # Enable the Proxy Protocol header parsing for the entry point ++ # proxyProtocol: ++ # trustedIPs: [] ++ # insecure: false ++ # + # One can apply Middlewares on an entrypoint + # https://doc.traefik.io/traefik/middlewares/overview/ + # https://doc.traefik.io/traefik/routing/entrypoints/#middlewares +``` + +## 16.1.0 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-19 + +* ✨ add optional ServiceMonitor & PrometheusRules CRDs (#425) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 7e335b5..9b5afc4 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -237,8 +237,46 @@ metrics: + prometheus: + entryPoint: metrics + # addRoutersLabels: true +- # statsd: +- # address: localhost:8125 ++ # statsd: ++ # address: localhost:8125 ++## ++## enable optional CRDs for Prometheus Operator ++## ++ # serviceMonitor: ++ # additionalLabels: ++ # foo: bar ++ # namespace: "another-namespace" ++ # namespaceSelector: {} ++ # metricRelabelings: [] ++ # - sourceLabels: [__name__] ++ # separator: ; ++ # regex: ^fluentd_output_status_buffer_(oldest|newest)_.+ ++ # replacement: $1 ++ # action: drop ++ # relabelings: [] ++ # - sourceLabels: [__meta_kubernetes_pod_node_name] ++ # separator: ; ++ # regex: ^(.*)$ ++ # targetLabel: nodename ++ # replacement: $1 ++ # action: replace ++ # jobLabel: traefik ++ # scrapeInterval: 30s ++ # scrapeTimeout: 5s ++ # honorLabels: true ++ # prometheusRule: ++ # additionalLabels: {} ++ # namespace: "another-namespace" ++ # rules: ++ # - alert: TraefikDown ++ # expr: up{job="traefik"} == 0 ++ # for: 5m ++ # labels: ++ # context: traefik ++ # severity: warning ++ # annotations: ++ # summary: "Traefik Down" ++ # description: "{{ $labels.pod }} on {{ $labels.nodename }} is down" + + tracing: {} + # instana: +``` + +## 16.0.0 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-19 + +* :fire: Remove `Pilot` and `fallbackApiVersion` (#665) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 03fdaed..7e335b5 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -84,15 +84,6 @@ ingressClass: + # true is not unit-testable yet, pending https://github.com/rancher/helm-unittest/pull/12 + enabled: false + isDefaultClass: false +- # Use to force a networking.k8s.io API Version for certain CI/CD applications. E.g. "v1beta1" +- fallbackApiVersion: "" +- +-# Activate Pilot integration +-pilot: +- enabled: false +- token: "" +- # Toggle Pilot Dashboard +- # dashboard: false + + # Enable experimental features + experimental: +``` + +## 15.3.1 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-18 + +* :art: Improve `IngressRoute` structure (#674) + + +## 15.3.0 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-18 + +* 📌 Add capacity to enable User-facing role + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 76aac93..03fdaed 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -553,10 +553,12 @@ hostNetwork: false + # Whether Role Based Access Control objects like roles and rolebindings should be created + rbac: + enabled: true +- + # If set to false, installs ClusterRole and ClusterRoleBinding so Traefik can be used across namespaces. + # If set to true, installs Role and RoleBinding. Providers will only watch target namespace. + namespaced: false ++ # Enable user-facing roles ++ # https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles ++ # aggregateTo: [ "admin" ] + + # Enable to create a PodSecurityPolicy and assign it to the Service Account via RoleBinding or ClusterRoleBinding + podSecurityPolicy: +``` + +## 15.2.2 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-17 + +* Fix provider namespace changes + + +## 15.2.1 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-17 + +* 🐛 fix provider namespace changes + + +## 15.2.0 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-17 + +* :bug: Allow to watch on specific namespaces without using rbac.namespaced (#666) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 781ac15..76aac93 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -555,7 +555,7 @@ rbac: + enabled: true + + # If set to false, installs ClusterRole and ClusterRoleBinding so Traefik can be used across namespaces. +- # If set to true, installs namespace-specific Role and RoleBinding and requires provider configuration be set to that same namespace ++ # If set to true, installs Role and RoleBinding. Providers will only watch target namespace. + namespaced: false + + # Enable to create a PodSecurityPolicy and assign it to the Service Account via RoleBinding or ClusterRoleBinding +``` + +## 15.1.1 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-17 + +* :goal_net: Fail gracefully when http3 is not enabled correctly (#667) + + +## 15.1.0 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-14 + +* :sparkles: add optional topologySpreadConstraints (#663) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index fc2c371..781ac15 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -593,6 +593,15 @@ affinity: {} + + nodeSelector: {} + tolerations: [] ++topologySpreadConstraints: [] ++# # This example topologySpreadConstraints forces the scheduler to put traefik pods ++# # on nodes where no other traefik pods are scheduled. ++# - labelSelector: ++# matchLabels: ++# app: '{{ template "traefik.name" . }}' ++# maxSkew: 1 ++# topologyKey: kubernetes.io/hostname ++# whenUnsatisfiable: DoNotSchedule + + # Pods can have priority. + # Priority indicates the importance of a Pod relative to other Pods. +``` + +## 15.0.0 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-13 + +* :rocket: Enable TLS by default on `websecure` port (#657) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 400a29a..fc2c371 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -389,7 +389,7 @@ ports: + # Set TLS at the entrypoint + # https://doc.traefik.io/traefik/routing/entrypoints/#tls + tls: +- enabled: false ++ enabled: true + # this is the name of a TLSOption definition + options: "" + certResolver: "" +``` + +## 14.0.2 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-13 + +* :memo: Add Changelog (#661) + + +## 14.0.1 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-11 + +* :memo: Update workaround for permissions 660 on acme.json + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index a4e4ff2..400a29a 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -45,10 +45,10 @@ deployment: + # Additional initContainers (e.g. for setting file permission as shown below) + initContainers: [] + # The "volume-permissions" init container is required if you run into permission issues. +- # Related issue: https://github.com/traefik/traefik/issues/6972 ++ # Related issue: https://github.com/traefik/traefik/issues/6825 + # - name: volume-permissions +- # image: busybox:1.31.1 +- # command: ["sh", "-c", "chmod -Rv 600 /data/*"] ++ # image: busybox:1.35 ++ # command: ["sh", "-c", "touch /data/acme.json && chmod -Rv 600 /data/* && chown 65532:65532 /data/acme.json"] + # volumeMounts: + # - name: data + # mountPath: /data +``` + +## 14.0.0 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-11 + +* Limit rbac to only required resources for Ingress and CRD providers + + +## 13.0.1 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-11 + +* Add helper function for common labels + + +## 13.0.0 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-11 + +* Moved list object to individual objects + + +## 12.0.7 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-10 + +* :lipstick: Affinity templating and example (#557) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 4431c36..a4e4ff2 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -578,19 +578,19 @@ resources: {} + # limits: + # cpu: "300m" + # memory: "150Mi" ++ ++# This example pod anti-affinity forces the scheduler to put traefik pods ++# on nodes where no other traefik pods are scheduled. ++# It should be used when hostNetwork: true to prevent port conflicts + affinity: {} +-# # This example pod anti-affinity forces the scheduler to put traefik pods +-# # on nodes where no other traefik pods are scheduled. +-# # It should be used when hostNetwork: true to prevent port conflicts +-# podAntiAffinity: +-# requiredDuringSchedulingIgnoredDuringExecution: +-# - labelSelector: +-# matchExpressions: +-# - key: app.kubernetes.io/name +-# operator: In +-# values: +-# - {{ template "traefik.name" . }} +-# topologyKey: kubernetes.io/hostname ++# podAntiAffinity: ++# requiredDuringSchedulingIgnoredDuringExecution: ++# - labelSelector: ++# matchLabels: ++# app.kubernetes.io/name: '{{ template "traefik.name" . }}' ++# app.kubernetes.io/instance: '{{ .Release.Name }}' ++# topologyKey: kubernetes.io/hostname ++ + nodeSelector: {} + tolerations: [] + +``` + +## 12.0.6 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-10 + +* :bug: Ignore kustomization file used for CRDs update (#653) + + +## 12.0.5 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-10 + +* :memo: Establish Traefik & CRD update process + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 3526729..4431c36 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -342,6 +342,7 @@ ports: + + # Override the liveness/readiness port. This is useful to integrate traefik + # with an external Load Balancer that performs healthchecks. ++ # Default: ports.traefik.port + # healthchecksPort: 9000 + + # Override the liveness/readiness scheme. Useful for getting ping to +``` + +## 12.0.4 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-10 + +* Allows ingressClass to be used without semver-compatible image tag + + +## 12.0.3 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-10 + +* :bug: Should check hostNetwork when hostPort != containerPort + + +## 12.0.2 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-07 + +* :goal_net: Fail gracefully when hostNetwork is enabled and hostPort != containerPort + + +## 12.0.1 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-07 + +* :bug: Fix a typo on `behavior` for HPA v2 + + +## 12.0.0 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-06 + +* Update default HPA API Version to `v2` and add support for behavior (#518) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 2bd51f8..3526729 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -488,11 +488,22 @@ autoscaling: + # - type: Resource + # resource: + # name: cpu +-# targetAverageUtilization: 60 ++# target: ++# type: Utilization ++# averageUtilization: 60 + # - type: Resource + # resource: + # name: memory +-# targetAverageUtilization: 60 ++# target: ++# type: Utilization ++# averageUtilization: 60 ++# behavior: ++# scaleDown: ++# stabilizationWindowSeconds: 300 ++# policies: ++# - type: Pods ++# value: 1 ++# periodSeconds: 60 + + # Enable persistence using Persistent Volume Claims + # ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ +``` + +## 11.1.1 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-05 + +* 🔊 add failure message when using maxUnavailable 0 and hostNetwork + + +## 11.1.0 ![AppVersion: 2.9.1](https://img.shields.io/static/v1?label=AppVersion&message=2.9.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-04 + +* Update Traefik to v2.9.1 + + +## 11.0.0 ![AppVersion: 2.8.7](https://img.shields.io/static/v1?label=AppVersion&message=2.8.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-04 + +* tweak default values to avoid downtime when updating + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 844cadc..2bd51f8 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -126,20 +126,20 @@ ingressRoute: + entryPoints: ["traefik"] + + rollingUpdate: +- maxUnavailable: 1 ++ maxUnavailable: 0 + maxSurge: 1 + + # Customize liveness and readiness probe values. + readinessProbe: + failureThreshold: 1 +- initialDelaySeconds: 10 ++ initialDelaySeconds: 2 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 2 + + livenessProbe: + failureThreshold: 3 +- initialDelaySeconds: 10 ++ initialDelaySeconds: 2 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 2 +``` + +## 10.33.0 ![AppVersion: 2.8.7](https://img.shields.io/static/v1?label=AppVersion&message=2.8.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-04 + +* :rocket: Add `extraObjects` value that allows creating adhoc resources + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index c926bd9..844cadc 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -598,3 +598,10 @@ securityContext: + + podSecurityContext: + fsGroup: 65532 ++ ++# ++# Extra objects to deploy (value evaluated as a template) ++# ++# In some cases, it can avoid the need for additional, extended or adhoc deployments. ++# See #595 for more details and traefik/tests/extra.yaml for example. ++extraObjects: [] +``` + +## 10.32.0 ![AppVersion: 2.8.7](https://img.shields.io/static/v1?label=AppVersion&message=2.8.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-03 + +* Add support setting middleware on entrypoint + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 3957448..c926bd9 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -397,6 +397,16 @@ ports: + # sans: + # - foo.example.com + # - bar.example.com ++ # ++ # One can apply Middlewares on an entrypoint ++ # https://doc.traefik.io/traefik/middlewares/overview/ ++ # https://doc.traefik.io/traefik/routing/entrypoints/#middlewares ++ # /!\ It introduces here a link between your static configuration and your dynamic configuration /!\ ++ # It follows the provider naming convention: https://doc.traefik.io/traefik/providers/overview/#provider-namespace ++ # middlewares: ++ # - namespace-name1@kubernetescrd ++ # - namespace-name2@kubernetescrd ++ middlewares: [] + metrics: + # When using hostNetwork, use another port to avoid conflict with node exporter: + # https://github.com/prometheus/prometheus/wiki/Default-port-allocations +``` + +## 10.31.0 ![AppVersion: 2.8.7](https://img.shields.io/static/v1?label=AppVersion&message=2.8.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-03 + +* Support setting dashboard entryPoints for ingressRoute resource + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index c9feb76..3957448 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -120,6 +120,10 @@ ingressRoute: + annotations: {} + # Additional ingressRoute labels (e.g. for filtering IngressRoute by custom labels) + labels: {} ++ # Specify the allowed entrypoints to use for the dashboard ingress route, (e.g. traefik, web, websecure). ++ # By default, it's using traefik entrypoint, which is not exposed. ++ # /!\ Do not expose your dashboard without any protection over the internet /!\ ++ entryPoints: ["traefik"] + + rollingUpdate: + maxUnavailable: 1 +``` + +## 10.30.2 ![AppVersion: 2.8.7](https://img.shields.io/static/v1?label=AppVersion&message=2.8.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-10-03 + +* :test_tube: Fail gracefully when asked to provide a service without ports + + +## 10.30.1 ![AppVersion: 2.8.7](https://img.shields.io/static/v1?label=AppVersion&message=2.8.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-09-30 + +* :arrow_up: Upgrade helm, ct & unittest (#638) + + +## 10.30.0 ![AppVersion: 2.8.7](https://img.shields.io/static/v1?label=AppVersion&message=2.8.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-09-30 + +* Add support HTTPS scheme for healthcheks + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index fed4a8a..c9feb76 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -340,6 +340,10 @@ ports: + # with an external Load Balancer that performs healthchecks. + # healthchecksPort: 9000 + ++ # Override the liveness/readiness scheme. Useful for getting ping to ++ # respond on websecure entryPoint. ++ # healthchecksScheme: HTTPS ++ + # Defines whether the port is exposed if service.type is LoadBalancer or + # NodePort. + # +``` + +## 10.29.0 ![AppVersion: 2.8.7](https://img.shields.io/static/v1?label=AppVersion&message=2.8.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-09-29 + +* Add missing tracing options + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index d1708cc..fed4a8a 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -247,12 +247,45 @@ metrics: + + tracing: {} + # instana: +- # enabled: true ++ # localAgentHost: 127.0.0.1 ++ # localAgentPort: 42699 ++ # logLevel: info ++ # enableAutoProfile: true + # datadog: + # localAgentHostPort: 127.0.0.1:8126 + # debug: false + # globalTag: "" + # prioritySampling: false ++ # jaeger: ++ # samplingServerURL: http://localhost:5778/sampling ++ # samplingType: const ++ # samplingParam: 1.0 ++ # localAgentHostPort: 127.0.0.1:6831 ++ # gen128Bit: false ++ # propagation: jaeger ++ # traceContextHeaderName: uber-trace-id ++ # disableAttemptReconnecting: true ++ # collector: ++ # endpoint: "" ++ # user: "" ++ # password: "" ++ # zipkin: ++ # httpEndpoint: http://localhost:9411/api/v2/spans ++ # sameSpan: false ++ # id128Bit: true ++ # sampleRate: 1.0 ++ # haystack: ++ # localAgentHost: 127.0.0.1 ++ # localAgentPort: 35000 ++ # globalTag: "" ++ # traceIDHeaderName: "" ++ # parentIDHeaderName: "" ++ # spanIDHeaderName: "" ++ # baggagePrefixHeaderName: "" ++ # elastic: ++ # serverURL: http://localhost:8200 ++ # secretToken: "" ++ # serviceEnvironment: "" + + globalArguments: + - "--global.checknewversion" +``` + +## 10.28.0 ![AppVersion: 2.8.7](https://img.shields.io/static/v1?label=AppVersion&message=2.8.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-09-29 + +* feat: add lifecycle for prestop and poststart + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 19a133c..d1708cc 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -59,6 +59,17 @@ deployment: + # Additional imagePullSecrets + imagePullSecrets: [] + # - name: myRegistryKeySecretName ++ # Pod lifecycle actions ++ lifecycle: {} ++ # preStop: ++ # exec: ++ # command: ["/bin/sh", "-c", "sleep 40"] ++ # postStart: ++ # httpGet: ++ # path: /ping ++ # port: 9000 ++ # host: localhost ++ # scheme: HTTP + + # Pod disruption budget + podDisruptionBudget: +``` + +## 10.27.0 ![AppVersion: 2.8.7](https://img.shields.io/static/v1?label=AppVersion&message=2.8.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-09-29 + +* feat: add create gateway option + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index d9c745e..19a133c 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -91,6 +91,8 @@ experimental: + enabled: false + kubernetesGateway: + enabled: false ++ gateway: ++ enabled: true + # certificate: + # group: "core" + # kind: "Secret" +``` + +## 10.26.1 ![AppVersion: 2.8.7](https://img.shields.io/static/v1?label=AppVersion&message=2.8.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-09-28 + +* 🐛 fix rbac templating (#636) + + +## 10.26.0 ![AppVersion: 2.8.7](https://img.shields.io/static/v1?label=AppVersion&message=2.8.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-09-28 + +* :bug: Fix ingressClass support when rbac.namespaced=true (#499) + + +## 10.25.1 ![AppVersion: 2.8.7](https://img.shields.io/static/v1?label=AppVersion&message=2.8.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-09-28 + +* Add ingressclasses to traefik role + + +## 10.25.0 ![AppVersion: 2.8.7](https://img.shields.io/static/v1?label=AppVersion&message=2.8.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-09-27 + +* Add TLSStore resource to chart + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index d4011c3..d9c745e 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -373,6 +373,15 @@ ports: + # - CurveP384 + tlsOptions: {} + ++# TLS Store are created as TLSStore CRDs. This is useful if you want to set a default certificate ++# https://doc.traefik.io/traefik/https/tls/#default-certificate ++# Example: ++# tlsStore: ++# default: ++# defaultCertificate: ++# secretName: tls-cert ++tlsStore: {} ++ + # Options for the main traefik service, where the entrypoints traffic comes + # from. + service: +``` + +## 10.24.5 ![AppVersion: 2.8.7](https://img.shields.io/static/v1?label=AppVersion&message=2.8.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-09-27 + +* Suggest an alternative port for metrics + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 81f2e85..d4011c3 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -344,6 +344,8 @@ ports: + # - foo.example.com + # - bar.example.com + metrics: ++ # When using hostNetwork, use another port to avoid conflict with node exporter: ++ # https://github.com/prometheus/prometheus/wiki/Default-port-allocations + port: 9100 + # hostPort: 9100 + # Defines whether the port is exposed if service.type is LoadBalancer or +``` + +## 10.24.4 ![AppVersion: 2.8.7](https://img.shields.io/static/v1?label=AppVersion&message=2.8.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-09-26 + +* Update Traefik to v2.8.7 + + +## 10.24.3 ![AppVersion: 2.8.5](https://img.shields.io/static/v1?label=AppVersion&message=2.8.5&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-09-14 + +* Update Traefik version to v2.8.5 + + +## 10.24.2 ![AppVersion: 2.8.4](https://img.shields.io/static/v1?label=AppVersion&message=2.8.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-09-05 + +* Update Traefik version to v2.8.4 + + +## 10.24.1 ![AppVersion: 2.8.0](https://img.shields.io/static/v1?label=AppVersion&message=2.8.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-08-29 + +* Update PodDisruptionBudget apiVersion to policy/v1 + + +## 10.24.0 ![AppVersion: 2.8.0](https://img.shields.io/static/v1?label=AppVersion&message=2.8.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-06-30 + +* Update Traefik version to v2.8.0 + + +## 10.23.0 ![AppVersion: 2.7.1](https://img.shields.io/static/v1?label=AppVersion&message=2.7.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-06-27 + +* Support environment variable usage for Datadog + + +## 10.22.0 ![AppVersion: 2.7.1](https://img.shields.io/static/v1?label=AppVersion&message=2.7.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-06-22 + +* Allow setting revisionHistoryLimit for Deployment and DaemonSet + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index d5785ab..81f2e85 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -14,6 +14,8 @@ deployment: + kind: Deployment + # Number of pods of the deployment (only applies when kind == Deployment) + replicas: 1 ++ # Number of old history to retain to allow rollback (If not set, default Kubernetes value is set to 10) ++ # revisionHistoryLimit: 1 + # Amount of time (in seconds) before Kubernetes will send the SIGKILL signal if Traefik does not shut down + terminationGracePeriodSeconds: 60 + # The minimum number of seconds Traefik needs to be up and running before the DaemonSet/Deployment controller considers it available +``` + +## 10.21.1 ![AppVersion: 2.7.1](https://img.shields.io/static/v1?label=AppVersion&message=2.7.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-06-15 + +* Update Traefik version to 2.7.1 + + +## 10.21.0 ![AppVersion: 2.7.0](https://img.shields.io/static/v1?label=AppVersion&message=2.7.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-06-15 + +* Support allowEmptyServices config for KubernetesCRD + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index e141e29..d5785ab 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -133,6 +133,7 @@ providers: + enabled: true + allowCrossNamespace: false + allowExternalNameServices: false ++ allowEmptyServices: false + # ingressClass: traefik-internal + # labelSelector: environment=production,method=traefik + namespaces: [] +``` + +## 10.20.1 ![AppVersion: 2.7.0](https://img.shields.io/static/v1?label=AppVersion&message=2.7.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-06-01 + +* Add Acme certificate resolver configuration + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index a16b107..e141e29 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -433,6 +433,27 @@ persistence: + annotations: {} + # subPath: "" # only mount a subpath of the Volume into the pod + ++certResolvers: {} ++# letsencrypt: ++# # for challenge options cf. https://doc.traefik.io/traefik/https/acme/ ++# email: email@example.com ++# dnsChallenge: ++# # also add the provider's required configuration under env ++# # or expand then from secrets/configmaps with envfrom ++# # cf. https://doc.traefik.io/traefik/https/acme/#providers ++# provider: digitalocean ++# # add futher options for the dns challenge as needed ++# # cf. https://doc.traefik.io/traefik/https/acme/#dnschallenge ++# delayBeforeCheck: 30 ++# resolvers: ++# - 1.1.1.1 ++# - 8.8.8.8 ++# tlsChallenge: true ++# httpChallenge: ++# entryPoint: "web" ++# # match the path to persistence ++# storage: /data/acme.json ++ + # If hostNetwork is true, runs traefik in the host network namespace + # To prevent unschedulabel pods due to port collisions, if hostNetwork=true + # and replicas>1, a pod anti-affinity is recommended and will be set if the +``` + +## 10.20.0 ![AppVersion: 2.7.0](https://img.shields.io/static/v1?label=AppVersion&message=2.7.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-05-25 + +* Update Traefik Proxy to v2.7.0 + + +## 10.19.5 ![AppVersion: 2.6.6](https://img.shields.io/static/v1?label=AppVersion&message=2.6.6&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-05-04 + +* Upgrade Traefik to 2.6.6 + + +## 10.19.4 ![AppVersion: 2.6.3](https://img.shields.io/static/v1?label=AppVersion&message=2.6.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-03-31 + +* Update Traefik dependency version to 2.6.3 + + +## 10.19.3 ![AppVersion: 2.6.2](https://img.shields.io/static/v1?label=AppVersion&message=2.6.2&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-03-30 + +* Update CRDs to match the ones defined in the reference documentation + + +## 10.19.2 ![AppVersion: 2.6.2](https://img.shields.io/static/v1?label=AppVersion&message=2.6.2&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-03-30 + +* Revert Traefik version to 2.6.2 + + +## 10.19.1 ![AppVersion: 2.6.3](https://img.shields.io/static/v1?label=AppVersion&message=2.6.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-03-30 + +* Update Traefik version to 2.6.3 + + +## 10.19.0 ![AppVersion: 2.6.2](https://img.shields.io/static/v1?label=AppVersion&message=2.6.2&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-03-28 + +* Support ingressClass option for KubernetesIngress provider + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 02ab704..a16b107 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -142,6 +142,7 @@ providers: + enabled: true + allowExternalNameServices: false + allowEmptyServices: false ++ # ingressClass: traefik-internal + # labelSelector: environment=production,method=traefik + namespaces: [] + # - "default" +``` + +## 10.18.0 ![AppVersion: 2.6.2](https://img.shields.io/static/v1?label=AppVersion&message=2.6.2&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-03-28 + +* Support liveness and readyness probes customization + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 15f1103..02ab704 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -110,6 +110,20 @@ rollingUpdate: + maxUnavailable: 1 + maxSurge: 1 + ++# Customize liveness and readiness probe values. ++readinessProbe: ++ failureThreshold: 1 ++ initialDelaySeconds: 10 ++ periodSeconds: 10 ++ successThreshold: 1 ++ timeoutSeconds: 2 ++ ++livenessProbe: ++ failureThreshold: 3 ++ initialDelaySeconds: 10 ++ periodSeconds: 10 ++ successThreshold: 1 ++ timeoutSeconds: 2 + + # + # Configure providers +``` + +## 10.17.0 ![AppVersion: 2.6.2](https://img.shields.io/static/v1?label=AppVersion&message=2.6.2&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-03-28 + +* Support Datadog tracing + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 4dccd1a..15f1103 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -217,6 +217,11 @@ metrics: + tracing: {} + # instana: + # enabled: true ++ # datadog: ++ # localAgentHostPort: 127.0.0.1:8126 ++ # debug: false ++ # globalTag: "" ++ # prioritySampling: false + + globalArguments: + - "--global.checknewversion" +``` + +## 10.16.1 ![AppVersion: 2.6.2](https://img.shields.io/static/v1?label=AppVersion&message=2.6.2&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-03-28 + +* Update Traefik version to 2.6.2 + + +## 10.16.0 ![AppVersion: 2.6.1](https://img.shields.io/static/v1?label=AppVersion&message=2.6.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-03-28 + +* Support allowEmptyServices for KubernetesIngress provider + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 1f9dbbe..4dccd1a 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -127,6 +127,7 @@ providers: + kubernetesIngress: + enabled: true + allowExternalNameServices: false ++ allowEmptyServices: false + # labelSelector: environment=production,method=traefik + namespaces: [] + # - "default" +``` + +## 10.15.0 ![AppVersion: 2.6.1](https://img.shields.io/static/v1?label=AppVersion&message=2.6.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-03-08 + +* Add metrics.prometheus.addRoutersLabels option + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index cd4d49b..1f9dbbe 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -209,6 +209,7 @@ metrics: + # protocol: udp + prometheus: + entryPoint: metrics ++ # addRoutersLabels: true + # statsd: + # address: localhost:8125 + +``` + +## 10.14.2 ![AppVersion: 2.6.1](https://img.shields.io/static/v1?label=AppVersion&message=2.6.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-02-18 + +* Update Traefik to v2.6.1 + + +## 10.14.1 ![AppVersion: 2.6.0](https://img.shields.io/static/v1?label=AppVersion&message=2.6.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-02-09 + +* Add missing inFlightConn TCP middleware CRD + + +## 10.14.0 ![AppVersion: 2.6.0](https://img.shields.io/static/v1?label=AppVersion&message=2.6.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-02-03 + +* Add experimental HTTP/3 support + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index d49122f..cd4d49b 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -83,6 +83,8 @@ pilot: + + # Enable experimental features + experimental: ++ http3: ++ enabled: false + plugins: + enabled: false + kubernetesGateway: +@@ -300,6 +302,10 @@ ports: + # The port protocol (TCP/UDP) + protocol: TCP + # nodePort: 32443 ++ # Enable HTTP/3. ++ # Requires enabling experimental http3 feature and tls. ++ # Note that you cannot have a UDP entrypoint with the same port. ++ # http3: true + # Set TLS at the entrypoint + # https://doc.traefik.io/traefik/routing/entrypoints/#tls + tls: +``` + +## 10.13.0 ![AppVersion: 2.6.0](https://img.shields.io/static/v1?label=AppVersion&message=2.6.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-02-01 + +* Add support for ipFamilies + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 32fce6f..d49122f 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -366,6 +366,11 @@ service: + # - 1.2.3.4 + # One of SingleStack, PreferDualStack, or RequireDualStack. + # ipFamilyPolicy: SingleStack ++ # List of IP families (e.g. IPv4 and/or IPv6). ++ # ref: https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services ++ # ipFamilies: ++ # - IPv4 ++ # - IPv6 + + ## Create HorizontalPodAutoscaler object. + ## +``` + +## 10.12.0 ![AppVersion: 2.6.0](https://img.shields.io/static/v1?label=AppVersion&message=2.6.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-02-01 + +* Add shareProcessNamespace option to podtemplate + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index ab25456..32fce6f 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -50,6 +50,8 @@ deployment: + # volumeMounts: + # - name: data + # mountPath: /data ++ # Use process namespace sharing ++ shareProcessNamespace: false + # Custom pod DNS policy. Apply if `hostNetwork: true` + # dnsPolicy: ClusterFirstWithHostNet + # Additional imagePullSecrets +``` + +## 10.11.1 ![AppVersion: 2.6.0](https://img.shields.io/static/v1?label=AppVersion&message=2.6.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-01-31 + +* Fix anti-affinity example + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 8c72905..ab25456 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -438,13 +438,13 @@ affinity: {} + # # It should be used when hostNetwork: true to prevent port conflicts + # podAntiAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: +-# - labelSelector: +-# matchExpressions: +-# - key: app +-# operator: In +-# values: +-# - {{ template "traefik.name" . }} +-# topologyKey: failure-domain.beta.kubernetes.io/zone ++# - labelSelector: ++# matchExpressions: ++# - key: app.kubernetes.io/name ++# operator: In ++# values: ++# - {{ template "traefik.name" . }} ++# topologyKey: kubernetes.io/hostname + nodeSelector: {} + tolerations: [] + +``` + +## 10.11.0 ![AppVersion: 2.6.0](https://img.shields.io/static/v1?label=AppVersion&message=2.6.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-01-31 + +* Add setting to enable Instana tracing + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 7fe4a2c..8c72905 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -208,6 +208,10 @@ metrics: + # statsd: + # address: localhost:8125 + ++tracing: {} ++ # instana: ++ # enabled: true ++ + globalArguments: + - "--global.checknewversion" + - "--global.sendanonymoususage" +``` + +## 10.10.0 ![AppVersion: 2.6.0](https://img.shields.io/static/v1?label=AppVersion&message=2.6.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2022-01-31 + +* Update Traefik to v2.6 + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 8ae4bd8..7fe4a2c 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -85,9 +85,8 @@ experimental: + enabled: false + kubernetesGateway: + enabled: false +- appLabelSelector: "traefik" +- certificates: [] +- # - group: "core" ++ # certificate: ++ # group: "core" + # kind: "Secret" + # name: "mysecret" + # By default, Gateway would be created to the Namespace you are deploying Traefik to. +``` + +## 10.9.1 ![AppVersion: 2.5.6](https://img.shields.io/static/v1?label=AppVersion&message=2.5.6&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-12-24 + +* Bump traefik version to 2.5.6 + + +## 10.9.0 ![AppVersion: 2.5.4](https://img.shields.io/static/v1?label=AppVersion&message=2.5.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-12-20 + +* feat: add allowExternalNameServices to KubernetesIngress provider + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 79df205..8ae4bd8 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -123,6 +123,7 @@ providers: + + kubernetesIngress: + enabled: true ++ allowExternalNameServices: false + # labelSelector: environment=production,method=traefik + namespaces: [] + # - "default" +``` + +## 10.8.0 ![AppVersion: 2.5.4](https://img.shields.io/static/v1?label=AppVersion&message=2.5.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-12-20 + +* Add support to specify minReadySeconds on Deployment/DaemonSet + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 7e9186b..79df205 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -16,6 +16,8 @@ deployment: + replicas: 1 + # Amount of time (in seconds) before Kubernetes will send the SIGKILL signal if Traefik does not shut down + terminationGracePeriodSeconds: 60 ++ # The minimum number of seconds Traefik needs to be up and running before the DaemonSet/Deployment controller considers it available ++ minReadySeconds: 0 + # Additional deployment annotations (e.g. for jaeger-operator sidecar injection) + annotations: {} + # Additional deployment labels (e.g. for filtering deployment by custom labels) +``` + +## 10.7.1 ![AppVersion: 2.5.4](https://img.shields.io/static/v1?label=AppVersion&message=2.5.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-12-06 + +* Fix pod disruption when using percentages + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index e0655c8..7e9186b 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -52,13 +52,15 @@ deployment: + # dnsPolicy: ClusterFirstWithHostNet + # Additional imagePullSecrets + imagePullSecrets: [] +- # - name: myRegistryKeySecretName ++ # - name: myRegistryKeySecretName + + # Pod disruption budget + podDisruptionBudget: + enabled: false + # maxUnavailable: 1 ++ # maxUnavailable: 33% + # minAvailable: 0 ++ # minAvailable: 25% + + # Use ingressClass. Ignored if Traefik version < 2.3 / kubernetes < 1.18.x + ingressClass: +``` + +## 10.7.0 ![AppVersion: 2.5.4](https://img.shields.io/static/v1?label=AppVersion&message=2.5.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-12-06 + +* Add support for ipFamilyPolicy + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 3ec7105..e0655c8 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -343,8 +343,8 @@ service: + annotationsUDP: {} + # Additional service labels (e.g. for filtering Service by custom labels) + labels: {} +- # Additional entries here will be added to the service spec. Cannot contains +- # type, selector or ports entries. ++ # Additional entries here will be added to the service spec. ++ # Cannot contain type, selector or ports entries. + spec: {} + # externalTrafficPolicy: Cluster + # loadBalancerIP: "1.2.3.4" +@@ -354,6 +354,8 @@ service: + # - 172.16.0.0/16 + externalIPs: [] + # - 1.2.3.4 ++ # One of SingleStack, PreferDualStack, or RequireDualStack. ++ # ipFamilyPolicy: SingleStack + + ## Create HorizontalPodAutoscaler object. + ## +``` + +## 10.6.2 ![AppVersion: 2.5.4](https://img.shields.io/static/v1?label=AppVersion&message=2.5.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-11-15 + +* Bump Traefik version to 2.5.4 + + +## 10.6.1 ![AppVersion: 2.5.3](https://img.shields.io/static/v1?label=AppVersion&message=2.5.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-11-05 + +* Add missing Gateway API resources to ClusterRole + + +## 10.6.0 ![AppVersion: 2.5.3](https://img.shields.io/static/v1?label=AppVersion&message=2.5.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-10-13 + +* feat: allow termination grace period to be configurable + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index f06ebc6..3ec7105 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -14,6 +14,8 @@ deployment: + kind: Deployment + # Number of pods of the deployment (only applies when kind == Deployment) + replicas: 1 ++ # Amount of time (in seconds) before Kubernetes will send the SIGKILL signal if Traefik does not shut down ++ terminationGracePeriodSeconds: 60 + # Additional deployment annotations (e.g. for jaeger-operator sidecar injection) + annotations: {} + # Additional deployment labels (e.g. for filtering deployment by custom labels) +``` + +## 10.5.0 ![AppVersion: 2.5.3](https://img.shields.io/static/v1?label=AppVersion&message=2.5.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-10-13 + +* feat: add allowExternalNameServices to Kubernetes CRD provider + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 3bcb350..f06ebc6 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -109,6 +109,7 @@ providers: + kubernetesCRD: + enabled: true + allowCrossNamespace: false ++ allowExternalNameServices: false + # ingressClass: traefik-internal + # labelSelector: environment=production,method=traefik + namespaces: [] +``` + +## 10.4.2 ![AppVersion: 2.5.3](https://img.shields.io/static/v1?label=AppVersion&message=2.5.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-10-13 + +* fix(crd): add permissionsPolicy to headers middleware + + +## 10.4.1 ![AppVersion: 2.5.3](https://img.shields.io/static/v1?label=AppVersion&message=2.5.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-10-13 + +* fix(crd): add peerCertURI option to ServersTransport + + +## 10.4.0 ![AppVersion: 2.5.3](https://img.shields.io/static/v1?label=AppVersion&message=2.5.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-10-12 + +* Add Kubernetes CRD labelSelector and ingressClass options + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index f54f5fe..3bcb350 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -109,8 +109,11 @@ providers: + kubernetesCRD: + enabled: true + allowCrossNamespace: false ++ # ingressClass: traefik-internal ++ # labelSelector: environment=production,method=traefik + namespaces: [] + # - "default" ++ + kubernetesIngress: + enabled: true + # labelSelector: environment=production,method=traefik +``` + +## 10.3.6 ![AppVersion: 2.5.3](https://img.shields.io/static/v1?label=AppVersion&message=2.5.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-09-24 + +* Fix missing RequireAnyClientCert value to TLSOption CRD + + +## 10.3.5 ![AppVersion: 2.5.3](https://img.shields.io/static/v1?label=AppVersion&message=2.5.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-09-23 + +* Bump Traefik version to 2.5.3 + + +## 10.3.4 ![AppVersion: 2.5.1](https://img.shields.io/static/v1?label=AppVersion&message=2.5.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-09-17 + +* Add allowCrossNamespace option on kubernetesCRD provider + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 7e3a579..f54f5fe 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -108,6 +108,7 @@ rollingUpdate: + providers: + kubernetesCRD: + enabled: true ++ allowCrossNamespace: false + namespaces: [] + # - "default" + kubernetesIngress: +``` + +## 10.3.3 ![AppVersion: 2.5.1](https://img.shields.io/static/v1?label=AppVersion&message=2.5.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-09-17 + +* fix(crd): missing alpnProtocols in TLSOption + + +## 10.3.2 ![AppVersion: 2.5.1](https://img.shields.io/static/v1?label=AppVersion&message=2.5.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-08-23 + +* Releasing 2.5.1 + + +## 10.3.1 ![AppVersion: 2.5.0](https://img.shields.io/static/v1?label=AppVersion&message=2.5.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-08-20 + +* Fix Ingress RBAC for namespaced scoped deployment + + +## 10.3.0 ![AppVersion: 2.5.0](https://img.shields.io/static/v1?label=AppVersion&message=2.5.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-08-18 + +* Releasing Traefik 2.5.0 + + +## 10.2.0 ![AppVersion: 2.4.13](https://img.shields.io/static/v1?label=AppVersion&message=2.4.13&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-08-18 + +* Allow setting TCP and UDP service annotations separately + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 72a01ea..7e3a579 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -328,8 +328,12 @@ tlsOptions: {} + service: + enabled: true + type: LoadBalancer +- # Additional annotations (e.g. for cloud provider specific config) ++ # Additional annotations applied to both TCP and UDP services (e.g. for cloud provider specific config) + annotations: {} ++ # Additional annotations for TCP service only ++ annotationsTCP: {} ++ # Additional annotations for UDP service only ++ annotationsUDP: {} + # Additional service labels (e.g. for filtering Service by custom labels) + labels: {} + # Additional entries here will be added to the service spec. Cannot contains +``` + +## 10.1.6 ![AppVersion: 2.4.13](https://img.shields.io/static/v1?label=AppVersion&message=2.4.13&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-08-17 + +* fix: missing service labels + + +## 10.1.5 ![AppVersion: 2.4.13](https://img.shields.io/static/v1?label=AppVersion&message=2.4.13&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-08-17 + +* fix(pvc-annotaions): see traefik/traefik-helm-chart#471 + + +## 10.1.4 ![AppVersion: 2.4.13](https://img.shields.io/static/v1?label=AppVersion&message=2.4.13&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-08-17 + +* fix(ingressclass): fallbackApiVersion default shouldn't be `nil` + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 04d336c..72a01ea 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -64,7 +64,7 @@ ingressClass: + enabled: false + isDefaultClass: false + # Use to force a networking.k8s.io API Version for certain CI/CD applications. E.g. "v1beta1" +- fallbackApiVersion: ++ fallbackApiVersion: "" + + # Activate Pilot integration + pilot: +``` + +## 10.1.3 ![AppVersion: 2.4.13](https://img.shields.io/static/v1?label=AppVersion&message=2.4.13&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-08-16 + +* Move Prometheus annotations to Pods + + +## 10.1.2 ![AppVersion: 2.4.13](https://img.shields.io/static/v1?label=AppVersion&message=2.4.13&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-08-10 + +* Version bumped 2.4.13 + + +## 10.1.1 ![AppVersion: 2.4.9](https://img.shields.io/static/v1?label=AppVersion&message=2.4.9&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-07-20 + +* Fixing Prometheus.io/port annotation + + +## 10.1.0 ![AppVersion: 2.4.9](https://img.shields.io/static/v1?label=AppVersion&message=2.4.9&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-07-20 + +* Add metrics framework, and prom annotations + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index f6e370a..04d336c 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -186,6 +186,17 @@ logs: + # Authorization: drop + # Content-Type: keep + ++metrics: ++ # datadog: ++ # address: 127.0.0.1:8125 ++ # influxdb: ++ # address: localhost:8089 ++ # protocol: udp ++ prometheus: ++ entryPoint: metrics ++ # statsd: ++ # address: localhost:8125 ++ + globalArguments: + - "--global.checknewversion" + - "--global.sendanonymoususage" +@@ -284,6 +295,20 @@ ports: + # sans: + # - foo.example.com + # - bar.example.com ++ metrics: ++ port: 9100 ++ # hostPort: 9100 ++ # Defines whether the port is exposed if service.type is LoadBalancer or ++ # NodePort. ++ # ++ # You may not want to expose the metrics port on production deployments. ++ # If you want to access it from outside of your cluster, ++ # use `kubectl port-forward` or create a secure ingress ++ expose: false ++ # The exposed port for this service ++ exposedPort: 9100 ++ # The port protocol (TCP/UDP) ++ protocol: TCP + + # TLS Options are created as TLSOption CRDs + # https://doc.traefik.io/traefik/https/tls/#tls-options +``` + +## 10.0.2 ![AppVersion: 2.4.9](https://img.shields.io/static/v1?label=AppVersion&message=2.4.9&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-07-14 + +* feat(gateway): introduces param / pick Namespace installing Gateway + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 9bf90ea..f6e370a 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -84,6 +84,9 @@ experimental: + # - group: "core" + # kind: "Secret" + # name: "mysecret" ++ # By default, Gateway would be created to the Namespace you are deploying Traefik to. ++ # You may create that Gateway in another namespace, setting its name below: ++ # namespace: default + + # Create an IngressRoute for the dashboard + ingressRoute: +``` + +## 10.0.1 ![AppVersion: 2.4.9](https://img.shields.io/static/v1?label=AppVersion&message=2.4.9&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-07-14 + +* Add RBAC for middlewaretcps + + +## 10.0.0 ![AppVersion: 2.4.9](https://img.shields.io/static/v1?label=AppVersion&message=2.4.9&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-07-07 + +* Update CRD versions + + +## 9.20.1 ![AppVersion: 2.4.8](https://img.shields.io/static/v1?label=AppVersion&message=2.4.8&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-07-05 + +* Revert CRD templating + + +## 9.20.0 ![AppVersion: 2.4.8](https://img.shields.io/static/v1?label=AppVersion&message=2.4.8&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-07-05 + +* Add support for apiextensions v1 CRDs + + +## 9.19.2 ![AppVersion: 2.4.8](https://img.shields.io/static/v1?label=AppVersion&message=2.4.8&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-06-16 + +* Add name-metadata for service "List" object + + +## 9.19.1 ![AppVersion: 2.4.8](https://img.shields.io/static/v1?label=AppVersion&message=2.4.8&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-05-13 + +* fix simple typo + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index b30afac..9bf90ea 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -363,7 +363,7 @@ rbac: + # If set to true, installs namespace-specific Role and RoleBinding and requires provider configuration be set to that same namespace + namespaced: false + +-# Enable to create a PodSecurityPolicy and assign it to the Service Account via RoleBindin or ClusterRoleBinding ++# Enable to create a PodSecurityPolicy and assign it to the Service Account via RoleBinding or ClusterRoleBinding + podSecurityPolicy: + enabled: false + +``` + +## 9.19.0 ![AppVersion: 2.4.8](https://img.shields.io/static/v1?label=AppVersion&message=2.4.8&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-04-29 + +* Fix IngressClass api version + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 0aa2d6b..b30afac 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -63,6 +63,8 @@ ingressClass: + # true is not unit-testable yet, pending https://github.com/rancher/helm-unittest/pull/12 + enabled: false + isDefaultClass: false ++ # Use to force a networking.k8s.io API Version for certain CI/CD applications. E.g. "v1beta1" ++ fallbackApiVersion: + + # Activate Pilot integration + pilot: +``` + +## 9.18.3 ![AppVersion: 2.4.8](https://img.shields.io/static/v1?label=AppVersion&message=2.4.8&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-04-26 + +* Fix: ignore provider namespace args on disabled + + +## 9.18.2 ![AppVersion: 2.4.8](https://img.shields.io/static/v1?label=AppVersion&message=2.4.8&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-04-02 + +* Fix pilot dashboard deactivation + + +## 9.18.1 ![AppVersion: 2.4.8](https://img.shields.io/static/v1?label=AppVersion&message=2.4.8&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-03-29 + +* Do not disable Traefik Pilot in the dashboard by default + + +## 9.18.0 ![AppVersion: 2.4.8](https://img.shields.io/static/v1?label=AppVersion&message=2.4.8&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-03-24 + +* Add an option to toggle the pilot dashboard + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 017f771..0aa2d6b 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -68,6 +68,8 @@ ingressClass: + pilot: + enabled: false + token: "" ++ # Toggle Pilot Dashboard ++ # dashboard: false + + # Enable experimental features + experimental: +``` + +## 9.17.6 ![AppVersion: 2.4.8](https://img.shields.io/static/v1?label=AppVersion&message=2.4.8&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-03-24 + +* Bump Traefik to 2.4.8 + + +## 9.17.5 ![AppVersion: 2.4.7](https://img.shields.io/static/v1?label=AppVersion&message=2.4.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-03-17 + +* feat(labelSelector): option matching Ingresses based on labelSelectors + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 868a985..017f771 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -105,6 +105,7 @@ providers: + # - "default" + kubernetesIngress: + enabled: true ++ # labelSelector: environment=production,method=traefik + namespaces: [] + # - "default" + # IP used for Kubernetes Ingress endpoints +``` + +## 9.17.4 ![AppVersion: 2.4.7](https://img.shields.io/static/v1?label=AppVersion&message=2.4.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-03-17 + +* Add helm resource-policy annotation on PVC + + +## 9.17.3 ![AppVersion: 2.4.7](https://img.shields.io/static/v1?label=AppVersion&message=2.4.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-03-17 + +* Throw error with explicit latest tag + + +## 9.17.2 ![AppVersion: 2.4.7](https://img.shields.io/static/v1?label=AppVersion&message=2.4.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-03-10 + +* fix(keywords): removed by mistake + + +## 9.17.1 ![AppVersion: 2.4.7](https://img.shields.io/static/v1?label=AppVersion&message=2.4.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-03-10 + +* feat(healthchecksPort): Support for overriding the liveness/readiness probes port + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 56abb93..868a985 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -120,6 +120,8 @@ providers: + # After the volume has been mounted, add the configs into traefik by using the `additionalArguments` list below, eg: + # additionalArguments: + # - "--providers.file.filename=/config/dynamic.toml" ++# - "--ping" ++# - "--ping.entrypoint=web" + volumes: [] + # - name: public-cert + # mountPath: "/certs" +@@ -225,6 +227,10 @@ ports: + # only. + # hostIP: 192.168.100.10 + ++ # Override the liveness/readiness port. This is useful to integrate traefik ++ # with an external Load Balancer that performs healthchecks. ++ # healthchecksPort: 9000 ++ + # Defines whether the port is exposed if service.type is LoadBalancer or + # NodePort. + # +``` + +## 9.16.2 ![AppVersion: 2.4.7](https://img.shields.io/static/v1?label=AppVersion&message=2.4.7&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-03-09 + +* Bump Traefik to 2.4.7 + + +## 9.16.1 ![AppVersion: 2.4.6](https://img.shields.io/static/v1?label=AppVersion&message=2.4.6&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-03-09 + +* Adding custom labels to deployment + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index ba24be7..56abb93 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -16,6 +16,8 @@ deployment: + replicas: 1 + # Additional deployment annotations (e.g. for jaeger-operator sidecar injection) + annotations: {} ++ # Additional deployment labels (e.g. for filtering deployment by custom labels) ++ labels: {} + # Additional pod annotations (e.g. for mesh injection or prometheus scraping) + podAnnotations: {} + # Additional Pod labels (e.g. for filtering Pod by custom labels) +``` + +## 9.15.2 ![AppVersion: 2.4.6](https://img.shields.io/static/v1?label=AppVersion&message=2.4.6&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-03-02 + +* Upgrade Traefik to 2.4.6 + + +## 9.15.1 ![AppVersion: 2.4.5](https://img.shields.io/static/v1?label=AppVersion&message=2.4.5&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-03-02 + +* Configurable PVC name + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 1e0e5a9..ba24be7 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -327,6 +327,7 @@ autoscaling: + # It will persist TLS certificates. + persistence: + enabled: false ++ name: data + # existingClaim: "" + accessMode: ReadWriteOnce + size: 128Mi +``` + +## 9.14.4 ![AppVersion: 2.4.5](https://img.shields.io/static/v1?label=AppVersion&message=2.4.5&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-03-02 + +* fix typo + + +## 9.14.3 ![AppVersion: 2.4.5](https://img.shields.io/static/v1?label=AppVersion&message=2.4.5&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-02-19 + +* Bump Traefik to 2.4.5 + + +## 9.14.2 ![AppVersion: 2.4.2](https://img.shields.io/static/v1?label=AppVersion&message=2.4.2&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-02-03 + +* docs: indent nit for dsdsocket example + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 56485ad..1e0e5a9 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -33,7 +33,7 @@ deployment: + additionalVolumes: [] + # - name: dsdsocket + # hostPath: +- # path: /var/run/statsd-exporter ++ # path: /var/run/statsd-exporter + # Additional initContainers (e.g. for setting file permission as shown below) + initContainers: [] + # The "volume-permissions" init container is required if you run into permission issues. +``` + +## 9.14.1 ![AppVersion: 2.4.2](https://img.shields.io/static/v1?label=AppVersion&message=2.4.2&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-02-03 + +* Update Traefik to 2.4.2 + + +## 9.14.0 ![AppVersion: 2.4.0](https://img.shields.io/static/v1?label=AppVersion&message=2.4.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-02-01 + +* Enable Kubernetes Gateway provider with an experimental flag + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 50cab94..56485ad 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -71,6 +71,13 @@ pilot: + experimental: + plugins: + enabled: false ++ kubernetesGateway: ++ enabled: false ++ appLabelSelector: "traefik" ++ certificates: [] ++ # - group: "core" ++ # kind: "Secret" ++ # name: "mysecret" + + # Create an IngressRoute for the dashboard + ingressRoute: +``` + +## 9.13.0 ![AppVersion: 2.4.0](https://img.shields.io/static/v1?label=AppVersion&message=2.4.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2021-01-22 + +* Update Traefik to 2.4 and add resources + + +## 9.12.3 ![AppVersion: 2.3.6](https://img.shields.io/static/v1?label=AppVersion&message=2.3.6&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-12-31 + +* Revert API Upgrade + + +## 9.12.2 ![AppVersion: 2.3.6](https://img.shields.io/static/v1?label=AppVersion&message=2.3.6&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-12-31 + +* Bump Traefik to 2.3.6 + + +## 9.12.1 ![AppVersion: 2.3.3](https://img.shields.io/static/v1?label=AppVersion&message=2.3.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-12-30 + +* Resolve #303, change CRD version from v1beta1 to v1 + + +## 9.12.0 ![AppVersion: 2.3.3](https://img.shields.io/static/v1?label=AppVersion&message=2.3.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-12-30 + +* Implement support for DaemonSet + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 60a721d..50cab94 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -10,7 +10,9 @@ image: + # + deployment: + enabled: true +- # Number of pods of the deployment ++ # Can be either Deployment or DaemonSet ++ kind: Deployment ++ # Number of pods of the deployment (only applies when kind == Deployment) + replicas: 1 + # Additional deployment annotations (e.g. for jaeger-operator sidecar injection) + annotations: {} +``` + +## 9.11.0 ![AppVersion: 2.3.3](https://img.shields.io/static/v1?label=AppVersion&message=2.3.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-11-20 + +* add podLabels - custom labels + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index a187df7..60a721d 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -16,6 +16,8 @@ deployment: + annotations: {} + # Additional pod annotations (e.g. for mesh injection or prometheus scraping) + podAnnotations: {} ++ # Additional Pod labels (e.g. for filtering Pod by custom labels) ++ podLabels: {} + # Additional containers (e.g. for metric offloading sidecars) + additionalContainers: [] + # https://docs.datadoghq.com/developers/dogstatsd/unix_socket/?tab=host +``` + +## 9.10.2 ![AppVersion: 2.3.3](https://img.shields.io/static/v1?label=AppVersion&message=2.3.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-11-20 + +* Bump Traefik to 2.3.3 + + +## 9.10.1 ![AppVersion: 2.3.1](https://img.shields.io/static/v1?label=AppVersion&message=2.3.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-11-04 + +* Specify IngressClass resource when checking for cluster capability + + +## 9.10.0 ![AppVersion: 2.3.1](https://img.shields.io/static/v1?label=AppVersion&message=2.3.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-11-03 + +* Add list of watched provider namespaces + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index e6b85ca..a187df7 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -88,8 +88,12 @@ rollingUpdate: + providers: + kubernetesCRD: + enabled: true ++ namespaces: [] ++ # - "default" + kubernetesIngress: + enabled: true ++ namespaces: [] ++ # - "default" + # IP used for Kubernetes Ingress endpoints + publishedService: + enabled: false +``` + +## 9.9.0 ![AppVersion: 2.3.1](https://img.shields.io/static/v1?label=AppVersion&message=2.3.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-11-03 + +* Add additionalVolumeMounts for traefik container + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 37dd151..e6b85ca 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -111,6 +111,12 @@ volumes: [] + # mountPath: "/config" + # type: configMap + ++# Additional volumeMounts to add to the Traefik container ++additionalVolumeMounts: [] ++ # For instance when using a logshipper for access logs ++ # - name: traefik-logs ++ # mountPath: /var/log/traefik ++ + # Logs + # https://docs.traefik.io/observability/logs/ + logs: +``` + +## 9.8.4 ![AppVersion: 2.3.1](https://img.shields.io/static/v1?label=AppVersion&message=2.3.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-11-03 + +* fix: multiple ImagePullSecrets + + +## 9.8.3 ![AppVersion: 2.3.1](https://img.shields.io/static/v1?label=AppVersion&message=2.3.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-10-30 + +* Add imagePullSecrets + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 87f60c0..37dd151 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -42,6 +42,9 @@ deployment: + # mountPath: /data + # Custom pod DNS policy. Apply if `hostNetwork: true` + # dnsPolicy: ClusterFirstWithHostNet ++ # Additional imagePullSecrets ++ imagePullSecrets: [] ++ # - name: myRegistryKeySecretName + + # Pod disruption budget + podDisruptionBudget: +``` + +## 9.8.2 ![AppVersion: 2.3.1](https://img.shields.io/static/v1?label=AppVersion&message=2.3.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-10-28 + +* Add chart repo to source + + +## 9.8.1 ![AppVersion: 2.3.1](https://img.shields.io/static/v1?label=AppVersion&message=2.3.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-10-23 + +* fix semver compare + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 4ca1f8f..87f60c0 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -1,7 +1,8 @@ + # Default values for Traefik + image: + name: traefik +- tag: 2.3.1 ++ # defaults to appVersion ++ tag: "" + pullPolicy: IfNotPresent + + # +``` + +## 9.8.0 ![AppVersion: 2.3.1](https://img.shields.io/static/v1?label=AppVersion&message=2.3.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-10-20 + +* feat: Enable entrypoint tls config + TLSOption + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index eee3622..4ca1f8f 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -231,6 +231,31 @@ ports: + # The port protocol (TCP/UDP) + protocol: TCP + # nodePort: 32443 ++ # Set TLS at the entrypoint ++ # https://doc.traefik.io/traefik/routing/entrypoints/#tls ++ tls: ++ enabled: false ++ # this is the name of a TLSOption definition ++ options: "" ++ certResolver: "" ++ domains: [] ++ # - main: example.com ++ # sans: ++ # - foo.example.com ++ # - bar.example.com ++ ++# TLS Options are created as TLSOption CRDs ++# https://doc.traefik.io/traefik/https/tls/#tls-options ++# Example: ++# tlsOptions: ++# default: ++# sniStrict: true ++# preferServerCipherSuites: true ++# foobar: ++# curvePreferences: ++# - CurveP521 ++# - CurveP384 ++tlsOptions: {} + + # Options for the main traefik service, where the entrypoints traffic comes + # from. +``` + +## 9.7.0 ![AppVersion: 2.3.1](https://img.shields.io/static/v1?label=AppVersion&message=2.3.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-10-15 + +* Add a configuration option for an emptyDir as plugin storage + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index b7153a1..eee3622 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -54,10 +54,16 @@ ingressClass: + enabled: false + isDefaultClass: false + ++# Activate Pilot integration + pilot: + enabled: false + token: "" + ++# Enable experimental features ++experimental: ++ plugins: ++ enabled: false ++ + # Create an IngressRoute for the dashboard + ingressRoute: + dashboard: +``` + +## 9.6.0 ![AppVersion: 2.3.1](https://img.shields.io/static/v1?label=AppVersion&message=2.3.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-10-15 + +* Add additional volumes for init and additional containers + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 9bac45e..b7153a1 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -17,6 +17,18 @@ deployment: + podAnnotations: {} + # Additional containers (e.g. for metric offloading sidecars) + additionalContainers: [] ++ # https://docs.datadoghq.com/developers/dogstatsd/unix_socket/?tab=host ++ # - name: socat-proxy ++ # image: alpine/socat:1.0.5 ++ # args: ["-s", "-u", "udp-recv:8125", "unix-sendto:/socket/socket"] ++ # volumeMounts: ++ # - name: dsdsocket ++ # mountPath: /socket ++ # Additional volumes available for use with initContainers and additionalContainers ++ additionalVolumes: [] ++ # - name: dsdsocket ++ # hostPath: ++ # path: /var/run/statsd-exporter + # Additional initContainers (e.g. for setting file permission as shown below) + initContainers: [] + # The "volume-permissions" init container is required if you run into permission issues. +``` + +## 9.5.2 ![AppVersion: 2.3.1](https://img.shields.io/static/v1?label=AppVersion&message=2.3.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-10-15 + +* Replace extensions with policy because of deprecation + + +## 9.5.1 ![AppVersion: 2.3.1](https://img.shields.io/static/v1?label=AppVersion&message=2.3.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-10-14 + +* Template custom volume name + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 5a8d8ea..9bac45e 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -76,7 +76,7 @@ providers: + # pathOverride: "" + + # +-# Add volumes to the traefik pod. ++# Add volumes to the traefik pod. The volume name will be passed to tpl. + # This can be used to mount a cert pair or a configmap that holds a config.toml file. + # After the volume has been mounted, add the configs into traefik by using the `additionalArguments` list below, eg: + # additionalArguments: +@@ -85,7 +85,7 @@ volumes: [] + # - name: public-cert + # mountPath: "/certs" + # type: secret +-# - name: configs ++# - name: '{{ printf "%s-configs" .Release.Name }}' + # mountPath: "/config" + # type: configMap + +``` + +## 9.5.0 ![AppVersion: 2.3.1](https://img.shields.io/static/v1?label=AppVersion&message=2.3.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-10-02 + +* Create PodSecurityPolicy and RBAC when needed. + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 8c4d866..5a8d8ea 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -281,6 +281,10 @@ rbac: + # If set to true, installs namespace-specific Role and RoleBinding and requires provider configuration be set to that same namespace + namespaced: false + ++# Enable to create a PodSecurityPolicy and assign it to the Service Account via RoleBindin or ClusterRoleBinding ++podSecurityPolicy: ++ enabled: false ++ + # The service account the pods will use to interact with the Kubernetes API + serviceAccount: + # If set, an existing service account is used +``` + +## 9.4.3 ![AppVersion: 2.3.1](https://img.shields.io/static/v1?label=AppVersion&message=2.3.1&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-10-02 + +* Update traefik to v2.3.1 + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 3df75a4..8c4d866 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -1,7 +1,7 @@ + # Default values for Traefik + image: + name: traefik +- tag: 2.3.0 ++ tag: 2.3.1 + pullPolicy: IfNotPresent + + # +``` + +## 9.4.2 ![AppVersion: 2.3.0](https://img.shields.io/static/v1?label=AppVersion&message=2.3.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-10-02 + +* Add Artifact Hub repository metadata file + + +## 9.4.1 ![AppVersion: 2.3.0](https://img.shields.io/static/v1?label=AppVersion&message=2.3.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-10-01 + +* Fix broken chart icon url + + +## 9.4.0 ![AppVersion: 2.3.0](https://img.shields.io/static/v1?label=AppVersion&message=2.3.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-10-01 + +* Allow to specify custom labels on Service + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index a6175ff..3df75a4 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -221,6 +221,8 @@ service: + type: LoadBalancer + # Additional annotations (e.g. for cloud provider specific config) + annotations: {} ++ # Additional service labels (e.g. for filtering Service by custom labels) ++ labels: {} + # Additional entries here will be added to the service spec. Cannot contains + # type, selector or ports entries. + spec: {} +``` + +## 9.3.0 ![AppVersion: 2.3.0](https://img.shields.io/static/v1?label=AppVersion&message=2.3.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-09-24 + +* Release Traefik 2.3 + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index fba955d..a6175ff 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -1,7 +1,7 @@ + # Default values for Traefik + image: + name: traefik +- tag: 2.2.8 ++ tag: 2.3.0 + pullPolicy: IfNotPresent + + # +@@ -36,6 +36,16 @@ podDisruptionBudget: + # maxUnavailable: 1 + # minAvailable: 0 + ++# Use ingressClass. Ignored if Traefik version < 2.3 / kubernetes < 1.18.x ++ingressClass: ++ # true is not unit-testable yet, pending https://github.com/rancher/helm-unittest/pull/12 ++ enabled: false ++ isDefaultClass: false ++ ++pilot: ++ enabled: false ++ token: "" ++ + # Create an IngressRoute for the dashboard + ingressRoute: + dashboard: +``` + +## 9.2.1 ![AppVersion: 2.2.8](https://img.shields.io/static/v1?label=AppVersion&message=2.2.8&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-09-18 + +* Add new helm url + + +## 9.2.0 ![AppVersion: 2.2.8](https://img.shields.io/static/v1?label=AppVersion&message=2.2.8&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-09-16 + +* chore: move to new organization. + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 9f52c39..fba955d 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -20,7 +20,7 @@ deployment: + # Additional initContainers (e.g. for setting file permission as shown below) + initContainers: [] + # The "volume-permissions" init container is required if you run into permission issues. +- # Related issue: https://github.com/containous/traefik/issues/6972 ++ # Related issue: https://github.com/traefik/traefik/issues/6972 + # - name: volume-permissions + # image: busybox:1.31.1 + # command: ["sh", "-c", "chmod -Rv 600 /data/*"] +``` + +## 9.1.1 ![AppVersion: 2.2.8](https://img.shields.io/static/v1?label=AppVersion&message=2.2.8&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-09-04 + +* Update reference to using kubectl proxy to kubectl port-forward + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 7b74a39..9f52c39 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -175,7 +175,7 @@ ports: + # + # You SHOULD NOT expose the traefik port on production deployments. + # If you want to access it from outside of your cluster, +- # use `kubectl proxy` or create a secure ingress ++ # use `kubectl port-forward` or create a secure ingress + expose: false + # The exposed port for this service + exposedPort: 9000 +``` + +## 9.1.0 ![AppVersion: 2.2.8](https://img.shields.io/static/v1?label=AppVersion&message=2.2.8&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-08-24 + +* PublishedService option + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index e161a14..7b74a39 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -58,6 +58,12 @@ providers: + enabled: true + kubernetesIngress: + enabled: true ++ # IP used for Kubernetes Ingress endpoints ++ publishedService: ++ enabled: false ++ # Published Kubernetes Service to copy status from. Format: namespace/servicename ++ # By default this Traefik service ++ # pathOverride: "" + + # + # Add volumes to the traefik pod. +``` + +## 9.0.0 ![AppVersion: 2.2.8](https://img.shields.io/static/v1?label=AppVersion&message=2.2.8&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-08-21 + +* feat: Move Chart apiVersion: v2 + + +## 8.13.3 ![AppVersion: 2.2.8](https://img.shields.io/static/v1?label=AppVersion&message=2.2.8&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-08-21 + +* bug: Check for port config + + +## 8.13.2 ![AppVersion: 2.2.8](https://img.shields.io/static/v1?label=AppVersion&message=2.2.8&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-08-19 + +* Fix log level configuration + + +## 8.13.1 ![AppVersion: 2.2.8](https://img.shields.io/static/v1?label=AppVersion&message=2.2.8&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-08-18 + +* Dont redirect to websecure by default + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 67276f7..e161a14 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -188,7 +188,7 @@ ports: + # Port Redirections + # Added in 2.2, you can make permanent redirects via entrypoints. + # https://docs.traefik.io/routing/entrypoints/#redirection +- redirectTo: websecure ++ # redirectTo: websecure + websecure: + port: 8443 + # hostPort: 8443 +``` + +## 8.13.0 ![AppVersion: 2.2.8](https://img.shields.io/static/v1?label=AppVersion&message=2.2.8&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-08-18 + +* Add logging, and http redirect config + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 6f79580..67276f7 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -73,6 +73,48 @@ volumes: [] + # mountPath: "/config" + # type: configMap + ++# Logs ++# https://docs.traefik.io/observability/logs/ ++logs: ++ # Traefik logs concern everything that happens to Traefik itself (startup, configuration, events, shutdown, and so on). ++ general: ++ # By default, the logs use a text format (common), but you can ++ # also ask for the json format in the format option ++ # format: json ++ # By default, the level is set to ERROR. Alternative logging levels are DEBUG, PANIC, FATAL, ERROR, WARN, and INFO. ++ level: ERROR ++ access: ++ # To enable access logs ++ enabled: false ++ # By default, logs are written using the Common Log Format (CLF). ++ # To write logs in JSON, use json in the format option. ++ # If the given format is unsupported, the default (CLF) is used instead. ++ # format: json ++ # To write the logs in an asynchronous fashion, specify a bufferingSize option. ++ # This option represents the number of log lines Traefik will keep in memory before writing ++ # them to the selected output. In some cases, this option can greatly help performances. ++ # bufferingSize: 100 ++ # Filtering https://docs.traefik.io/observability/access-logs/#filtering ++ filters: {} ++ # statuscodes: "200,300-302" ++ # retryattempts: true ++ # minduration: 10ms ++ # Fields ++ # https://docs.traefik.io/observability/access-logs/#limiting-the-fieldsincluding-headers ++ fields: ++ general: ++ defaultmode: keep ++ names: {} ++ # Examples: ++ # ClientUsername: drop ++ headers: ++ defaultmode: drop ++ names: {} ++ # Examples: ++ # User-Agent: redact ++ # Authorization: drop ++ # Content-Type: keep ++ + globalArguments: + - "--global.checknewversion" + - "--global.sendanonymoususage" +@@ -143,6 +185,10 @@ ports: + # Use nodeport if set. This is useful if you have configured Traefik in a + # LoadBalancer + # nodePort: 32080 ++ # Port Redirections ++ # Added in 2.2, you can make permanent redirects via entrypoints. ++ # https://docs.traefik.io/routing/entrypoints/#redirection ++ redirectTo: websecure + websecure: + port: 8443 + # hostPort: 8443 +``` + +## 8.12.0 ![AppVersion: 2.2.8](https://img.shields.io/static/v1?label=AppVersion&message=2.2.8&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-08-14 + +* Add image pull policy + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 10b3949..6f79580 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -2,6 +2,7 @@ + image: + name: traefik + tag: 2.2.8 ++ pullPolicy: IfNotPresent + + # + # Configure the deployment +``` + +## 8.11.0 ![AppVersion: 2.2.8](https://img.shields.io/static/v1?label=AppVersion&message=2.2.8&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-08-12 + +* Add dns policy option + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 80ddaaa..10b3949 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -26,6 +26,8 @@ deployment: + # volumeMounts: + # - name: data + # mountPath: /data ++ # Custom pod DNS policy. Apply if `hostNetwork: true` ++ # dnsPolicy: ClusterFirstWithHostNet + + # Pod disruption budget + podDisruptionBudget: +``` + +## 8.10.0 ![AppVersion: 2.2.8](https://img.shields.io/static/v1?label=AppVersion&message=2.2.8&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-08-11 + +* Add hostIp to port configuration + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 936ab92..80ddaaa 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -112,6 +112,12 @@ ports: + port: 9000 + # Use hostPort if set. + # hostPort: 9000 ++ # ++ # Use hostIP if set. If not set, Kubernetes will default to 0.0.0.0, which ++ # means it's listening on all your interfaces and all your IPs. You may want ++ # to set this value if you need traefik to listen on specific interface ++ # only. ++ # hostIP: 192.168.100.10 + + # Defines whether the port is exposed if service.type is LoadBalancer or + # NodePort. +``` + +## 8.9.2 ![AppVersion: 2.2.8](https://img.shields.io/static/v1?label=AppVersion&message=2.2.8&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-08-10 + +* Bump Traefik to 2.2.8 + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 42ee893..936ab92 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -1,7 +1,7 @@ + # Default values for Traefik + image: + name: traefik +- tag: 2.2.5 ++ tag: 2.2.8 + + # + # Configure the deployment +``` + +## 8.9.1 ![AppVersion: 2.2.5](https://img.shields.io/static/v1?label=AppVersion&message=2.2.5&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-07-15 + +* Upgrade traefik version + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index a7fb668..42ee893 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -1,7 +1,7 @@ + # Default values for Traefik + image: + name: traefik +- tag: 2.2.1 ++ tag: 2.2.5 + + # + # Configure the deployment +``` + +## 8.9.0 ![AppVersion: 2.2.1](https://img.shields.io/static/v1?label=AppVersion&message=2.2.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-07-08 + +* run init container to set proper permissions on volume + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 62e3a77..a7fb668 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -16,6 +16,16 @@ deployment: + podAnnotations: {} + # Additional containers (e.g. for metric offloading sidecars) + additionalContainers: [] ++ # Additional initContainers (e.g. for setting file permission as shown below) ++ initContainers: [] ++ # The "volume-permissions" init container is required if you run into permission issues. ++ # Related issue: https://github.com/containous/traefik/issues/6972 ++ # - name: volume-permissions ++ # image: busybox:1.31.1 ++ # command: ["sh", "-c", "chmod -Rv 600 /data/*"] ++ # volumeMounts: ++ # - name: data ++ # mountPath: /data + + # Pod disruption budget + podDisruptionBudget: +``` + +## 8.8.1 ![AppVersion: 2.2.1](https://img.shields.io/static/v1?label=AppVersion&message=2.2.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-07-02 + +* Additional container fix + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 85df29c..62e3a77 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -15,7 +15,7 @@ deployment: + # Additional pod annotations (e.g. for mesh injection or prometheus scraping) + podAnnotations: {} + # Additional containers (e.g. for metric offloading sidecars) +- additionalContainers: {} ++ additionalContainers: [] + + # Pod disruption budget + podDisruptionBudget: +``` + +## 8.8.0 ![AppVersion: 2.2.1](https://img.shields.io/static/v1?label=AppVersion&message=2.2.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-07-01 + +* added additionalContainers option to chart + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 6a9dfd8..85df29c 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -14,6 +14,8 @@ deployment: + annotations: {} + # Additional pod annotations (e.g. for mesh injection or prometheus scraping) + podAnnotations: {} ++ # Additional containers (e.g. for metric offloading sidecars) ++ additionalContainers: {} + + # Pod disruption budget + podDisruptionBudget: +``` + +## 8.7.2 ![AppVersion: 2.2.1](https://img.shields.io/static/v1?label=AppVersion&message=2.2.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-06-30 + +* Update image + + +## 8.7.1 ![AppVersion: 2.2.1](https://img.shields.io/static/v1?label=AppVersion&message=2.2.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-06-26 + +* Update values.yaml + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 05f9eab..6a9dfd8 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -196,7 +196,7 @@ rbac: + # If set to true, installs namespace-specific Role and RoleBinding and requires provider configuration be set to that same namespace + namespaced: false + +-# The service account the pods will use to interact with the Kubernates API ++# The service account the pods will use to interact with the Kubernetes API + serviceAccount: + # If set, an existing service account is used + # If not set, a service account is created automatically using the fullname template +``` + +## 8.7.0 ![AppVersion: 2.2.1](https://img.shields.io/static/v1?label=AppVersion&message=2.2.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-06-23 + +* Add option to disable providers + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 102ae00..05f9eab 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -34,6 +34,16 @@ rollingUpdate: + maxUnavailable: 1 + maxSurge: 1 + ++ ++# ++# Configure providers ++# ++providers: ++ kubernetesCRD: ++ enabled: true ++ kubernetesIngress: ++ enabled: true ++ + # + # Add volumes to the traefik pod. + # This can be used to mount a cert pair or a configmap that holds a config.toml file. +``` + +## 8.6.1 ![AppVersion: 2.2.1](https://img.shields.io/static/v1?label=AppVersion&message=2.2.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-06-18 + +* Fix read-only /tmp + + +## 8.6.0 ![AppVersion: 2.2.1](https://img.shields.io/static/v1?label=AppVersion&message=2.2.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-06-17 + +* Add existing PVC support(#158) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index b2f4fc3..102ae00 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -164,6 +164,7 @@ autoscaling: + # It will persist TLS certificates. + persistence: + enabled: false ++# existingClaim: "" + accessMode: ReadWriteOnce + size: 128Mi + # storageClass: "" +``` + +## 8.5.0 ![AppVersion: 2.2.1](https://img.shields.io/static/v1?label=AppVersion&message=2.2.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-06-16 + +* UDP support + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 9a9b668..b2f4fc3 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -100,11 +100,15 @@ ports: + expose: false + # The exposed port for this service + exposedPort: 9000 ++ # The port protocol (TCP/UDP) ++ protocol: TCP + web: + port: 8000 + # hostPort: 8000 + expose: true + exposedPort: 80 ++ # The port protocol (TCP/UDP) ++ protocol: TCP + # Use nodeport if set. This is useful if you have configured Traefik in a + # LoadBalancer + # nodePort: 32080 +@@ -113,6 +117,8 @@ ports: + # hostPort: 8443 + expose: true + exposedPort: 443 ++ # The port protocol (TCP/UDP) ++ protocol: TCP + # nodePort: 32443 + + # Options for the main traefik service, where the entrypoints traffic comes +``` + +## 8.4.1 ![AppVersion: 2.2.1](https://img.shields.io/static/v1?label=AppVersion&message=2.2.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-06-10 + +* Fix PDB with minAvailable set + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index e812b98..9a9b668 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -18,7 +18,7 @@ deployment: + # Pod disruption budget + podDisruptionBudget: + enabled: false +- maxUnavailable: 1 ++ # maxUnavailable: 1 + # minAvailable: 0 + + # Create an IngressRoute for the dashboard +``` + +## 8.4.0 ![AppVersion: 2.2.1](https://img.shields.io/static/v1?label=AppVersion&message=2.2.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-06-09 + +* Add pod disruption budget (#192) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 5f44e5c..e812b98 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -15,6 +15,12 @@ deployment: + # Additional pod annotations (e.g. for mesh injection or prometheus scraping) + podAnnotations: {} + ++# Pod disruption budget ++podDisruptionBudget: ++ enabled: false ++ maxUnavailable: 1 ++ # minAvailable: 0 ++ + # Create an IngressRoute for the dashboard + ingressRoute: + dashboard: +``` + +## 8.3.0 ![AppVersion: 2.2.1](https://img.shields.io/static/v1?label=AppVersion&message=2.2.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-06-08 + +* Add option to disable RBAC and ServiceAccount + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 96bba18..5f44e5c 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -165,6 +165,20 @@ persistence: + # affinity is left as default. + hostNetwork: false + ++# Whether Role Based Access Control objects like roles and rolebindings should be created ++rbac: ++ enabled: true ++ ++ # If set to false, installs ClusterRole and ClusterRoleBinding so Traefik can be used across namespaces. ++ # If set to true, installs namespace-specific Role and RoleBinding and requires provider configuration be set to that same namespace ++ namespaced: false ++ ++# The service account the pods will use to interact with the Kubernates API ++serviceAccount: ++ # If set, an existing service account is used ++ # If not set, a service account is created automatically using the fullname template ++ name: "" ++ + # Additional serviceAccount annotations (e.g. for oidc authentication) + serviceAccountAnnotations: {} + +``` + +## 8.2.1 ![AppVersion: 2.2.1](https://img.shields.io/static/v1?label=AppVersion&message=2.2.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-05-25 + +* Remove suggested providers.kubernetesingress value + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index e35bdf9..96bba18 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -50,9 +50,9 @@ globalArguments: + # Configure Traefik static configuration + # Additional arguments to be passed at Traefik's binary + # All available options available on https://docs.traefik.io/reference/static-configuration/cli/ +-## Use curly braces to pass values: `helm install --set="additionalArguments={--providers.kubernetesingress,--log.level=DEBUG}"` ++## Use curly braces to pass values: `helm install --set="additionalArguments={--providers.kubernetesingress.ingressclass=traefik-internal,--log.level=DEBUG}"` + additionalArguments: [] +-# - "--providers.kubernetesingress" ++# - "--providers.kubernetesingress.ingressclass=traefik-internal" + # - "--log.level=DEBUG" + + # Environment variables to be passed to Traefik's binary +``` + +## 8.2.0 ![AppVersion: 2.2.1](https://img.shields.io/static/v1?label=AppVersion&message=2.2.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-05-18 + +* Add kubernetes ingress by default + + +## 8.1.5 ![AppVersion: 2.2.1](https://img.shields.io/static/v1?label=AppVersion&message=2.2.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-05-18 + +* Fix example log params in values.yml + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index abe2334..e35bdf9 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -50,10 +50,10 @@ globalArguments: + # Configure Traefik static configuration + # Additional arguments to be passed at Traefik's binary + # All available options available on https://docs.traefik.io/reference/static-configuration/cli/ +-## Use curly braces to pass values: `helm install --set="additionalArguments={--providers.kubernetesingress,--logs.level=DEBUG}"` ++## Use curly braces to pass values: `helm install --set="additionalArguments={--providers.kubernetesingress,--log.level=DEBUG}"` + additionalArguments: [] + # - "--providers.kubernetesingress" +-# - "--logs.level=DEBUG" ++# - "--log.level=DEBUG" + + # Environment variables to be passed to Traefik's binary + env: [] +``` + +## 8.1.4 ![AppVersion: 2.2.1](https://img.shields.io/static/v1?label=AppVersion&message=2.2.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-04-30 + +* Update Traefik to v2.2.1 + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 57cc7e1..abe2334 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -1,7 +1,7 @@ + # Default values for Traefik + image: + name: traefik +- tag: 2.2.0 ++ tag: 2.2.1 + + # + # Configure the deployment +``` + +## 8.1.3 ![AppVersion: 2.2.0](https://img.shields.io/static/v1?label=AppVersion&message=2.2.0&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-04-29 + +* Clarify additionnal arguments log + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index d639f72..57cc7e1 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -50,9 +50,10 @@ globalArguments: + # Configure Traefik static configuration + # Additional arguments to be passed at Traefik's binary + # All available options available on https://docs.traefik.io/reference/static-configuration/cli/ +-## Use curly braces to pass values: `helm install --set="additionalArguments={--providers.kubernetesingress,--global.checknewversion=true}"` ++## Use curly braces to pass values: `helm install --set="additionalArguments={--providers.kubernetesingress,--logs.level=DEBUG}"` + additionalArguments: [] + # - "--providers.kubernetesingress" ++# - "--logs.level=DEBUG" + + # Environment variables to be passed to Traefik's binary + env: [] +``` + +## 8.1.2 ![AppVersion: 2.2.0](https://img.shields.io/static/v1?label=AppVersion&message=2.2.0&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-04-23 + +* Remove invalid flags. (#161) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 0e7aaef..d639f72 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -34,8 +34,6 @@ rollingUpdate: + # After the volume has been mounted, add the configs into traefik by using the `additionalArguments` list below, eg: + # additionalArguments: + # - "--providers.file.filename=/config/dynamic.toml" +-# - "--tls.certificates.certFile=/certs/tls.crt" +-# - "--tls.certificates.keyFile=/certs/tls.key" + volumes: [] + # - name: public-cert + # mountPath: "/certs" +``` + +## 8.1.1 ![AppVersion: 2.2.0](https://img.shields.io/static/v1?label=AppVersion&message=2.2.0&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-04-23 + +* clarify project philosophy and guidelines + + +## 8.1.0 ![AppVersion: 2.2.0](https://img.shields.io/static/v1?label=AppVersion&message=2.2.0&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-04-22 + +* Add priorityClassName & securityContext + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index d55a40a..0e7aaef 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -191,3 +191,20 @@ affinity: {} + # topologyKey: failure-domain.beta.kubernetes.io/zone + nodeSelector: {} + tolerations: [] ++ ++# Pods can have priority. ++# Priority indicates the importance of a Pod relative to other Pods. ++priorityClassName: "" ++ ++# Set the container security context ++# To run the container with ports below 1024 this will need to be adjust to run as root ++securityContext: ++ capabilities: ++ drop: [ALL] ++ readOnlyRootFilesystem: true ++ runAsGroup: 65532 ++ runAsNonRoot: true ++ runAsUser: 65532 ++ ++podSecurityContext: ++ fsGroup: 65532 +``` + +## 8.0.4 ![AppVersion: 2.2.0](https://img.shields.io/static/v1?label=AppVersion&message=2.2.0&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-04-20 + +* Possibility to bind environment variables via envFrom + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 7f8092e..d55a40a 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -71,6 +71,12 @@ env: [] + # name: secret-name + # key: secret-key + ++envFrom: [] ++# - configMapRef: ++# name: config-map-name ++# - secretRef: ++# name: secret-name ++ + # Configure ports + ports: + # The name of this one can't be changed as it is used for the readiness and +``` + +## 8.0.3 ![AppVersion: 2.2.0](https://img.shields.io/static/v1?label=AppVersion&message=2.2.0&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-04-15 + +* Add support for data volume subPath. (#147) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 152339b..7f8092e 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -152,6 +152,7 @@ persistence: + # storageClass: "" + path: /data + annotations: {} ++ # subPath: "" # only mount a subpath of the Volume into the pod + + # If hostNetwork is true, runs traefik in the host network namespace + # To prevent unschedulabel pods due to port collisions, if hostNetwork=true +``` + +## 8.0.2 ![AppVersion: 2.2.0](https://img.shields.io/static/v1?label=AppVersion&message=2.2.0&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-04-10 + +* Ability to add custom labels to dashboard's IngressRoute + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 5d294b7..152339b 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -21,6 +21,8 @@ ingressRoute: + enabled: true + # Additional ingressRoute annotations (e.g. for kubernetes.io/ingress.class) + annotations: {} ++ # Additional ingressRoute labels (e.g. for filtering IngressRoute by custom labels) ++ labels: {} + + rollingUpdate: + maxUnavailable: 1 +``` + +## 8.0.1 ![AppVersion: 2.2.0](https://img.shields.io/static/v1?label=AppVersion&message=2.2.0&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-04-10 + +* rbac does not need "pods" per documentation + + +## 8.0.0 ![AppVersion: 2.2.0](https://img.shields.io/static/v1?label=AppVersion&message=2.2.0&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-04-07 + +* follow helm best practices + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index e61a9fd..5d294b7 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -10,7 +10,7 @@ deployment: + enabled: true + # Number of pods of the deployment + replicas: 1 +- # Addtional deployment annotations (e.g. for jaeger-operator sidecar injection) ++ # Additional deployment annotations (e.g. for jaeger-operator sidecar injection) + annotations: {} + # Additional pod annotations (e.g. for mesh injection or prometheus scraping) + podAnnotations: {} +@@ -19,7 +19,7 @@ deployment: + ingressRoute: + dashboard: + enabled: true +- # Addtional ingressRoute annotations (e.g. for kubernetes.io/ingress.class) ++ # Additional ingressRoute annotations (e.g. for kubernetes.io/ingress.class) + annotations: {} + + rollingUpdate: +``` + +## 7.2.1 ![AppVersion: 2.2.0](https://img.shields.io/static/v1?label=AppVersion&message=2.2.0&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-04-07 + +* add annotations to ingressRoute + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 15d1c25..e61a9fd 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -19,6 +19,8 @@ deployment: + ingressRoute: + dashboard: + enabled: true ++ # Addtional ingressRoute annotations (e.g. for kubernetes.io/ingress.class) ++ annotations: {} + + rollingUpdate: + maxUnavailable: 1 +``` + +## 7.2.0 ![AppVersion: 2.2.0](https://img.shields.io/static/v1?label=AppVersion&message=2.2.0&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-04-03 + +* Add support for helm 2 + + +## 7.1.0 ![AppVersion: 2.2.0](https://img.shields.io/static/v1?label=AppVersion&message=2.2.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-31 + +* Add support for externalIPs + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 6d6d13f..15d1c25 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -116,6 +116,8 @@ service: + loadBalancerSourceRanges: [] + # - 192.168.0.1/32 + # - 172.16.0.0/16 ++ externalIPs: [] ++ # - 1.2.3.4 + + ## Create HorizontalPodAutoscaler object. + ## +``` + +## 7.0.0 ![AppVersion: 2.2.0](https://img.shields.io/static/v1?label=AppVersion&message=2.2.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-27 + +* Remove secretsEnv value key + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 1ac720d..6d6d13f 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -52,18 +52,20 @@ globalArguments: + additionalArguments: [] + # - "--providers.kubernetesingress" + +-# Secret to be set as environment variables to be passed to Traefik's binary +-secretEnv: [] +- # - name: SOME_VAR +- # secretName: my-secret-name +- # secretKey: my-secret-key +- + # Environment variables to be passed to Traefik's binary + env: [] +- # - name: SOME_VAR +- # value: some-var-value +- # - name: SOME_OTHER_VAR +- # value: some-other-var-value ++# - name: SOME_VAR ++# value: some-var-value ++# - name: SOME_VAR_FROM_CONFIG_MAP ++# valueFrom: ++# configMapRef: ++# name: configmap-name ++# key: config-key ++# - name: SOME_SECRET ++# valueFrom: ++# secretKeyRef: ++# name: secret-name ++# key: secret-key + + # Configure ports + ports: +``` + +## 6.4.0 ![AppVersion: 2.2.0](https://img.shields.io/static/v1?label=AppVersion&message=2.2.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-27 + +* Add ability to set serviceAccount annotations + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 85abe42..1ac720d 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -151,6 +151,9 @@ persistence: + # affinity is left as default. + hostNetwork: false + ++# Additional serviceAccount annotations (e.g. for oidc authentication) ++serviceAccountAnnotations: {} ++ + resources: {} + # requests: + # cpu: "100m" +``` + +## 6.3.0 ![AppVersion: 2.2.0](https://img.shields.io/static/v1?label=AppVersion&message=2.2.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-27 + +* hpa + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 2f5d132..85abe42 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -115,6 +115,22 @@ service: + # - 192.168.0.1/32 + # - 172.16.0.0/16 + ++## Create HorizontalPodAutoscaler object. ++## ++autoscaling: ++ enabled: false ++# minReplicas: 1 ++# maxReplicas: 10 ++# metrics: ++# - type: Resource ++# resource: ++# name: cpu ++# targetAverageUtilization: 60 ++# - type: Resource ++# resource: ++# name: memory ++# targetAverageUtilization: 60 ++ + # Enable persistence using Persistent Volume Claims + # ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + # After the pvc has been mounted, add the configs into traefik by using the `additionalArguments` list below, eg: +``` + +## 6.2.0 ![AppVersion: 2.2.0](https://img.shields.io/static/v1?label=AppVersion&message=2.2.0&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-26 + +* Update to v2.2 (#96) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index ebd2fde..2f5d132 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -1,7 +1,7 @@ + # Default values for Traefik + image: + name: traefik +- tag: 2.1.8 ++ tag: 2.2.0 + + # + # Configure the deployment +``` + +## 6.1.2 ![AppVersion: 2.1.8](https://img.shields.io/static/v1?label=AppVersion&message=2.1.8&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-20 + +* Upgrade traefik version + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 65c7665..ebd2fde 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -1,7 +1,7 @@ + # Default values for Traefik + image: + name: traefik +- tag: 2.1.4 ++ tag: 2.1.8 + + # + # Configure the deployment +``` + +## 6.1.1 ![AppVersion: 2.1.4](https://img.shields.io/static/v1?label=AppVersion&message=2.1.4&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-20 + +* Upgrade traefik version + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 89c7ac1..65c7665 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -1,7 +1,7 @@ + # Default values for Traefik + image: + name: traefik +- tag: 2.1.3 ++ tag: 2.1.4 + + # + # Configure the deployment +``` + +## 6.1.0 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-20 + +* Add ability to add annotations to deployment + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 8d66111..89c7ac1 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -10,6 +10,8 @@ deployment: + enabled: true + # Number of pods of the deployment + replicas: 1 ++ # Addtional deployment annotations (e.g. for jaeger-operator sidecar injection) ++ annotations: {} + # Additional pod annotations (e.g. for mesh injection or prometheus scraping) + podAnnotations: {} + +``` + +## 6.0.2 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-16 + +* Correct storage class key name + + +## 6.0.1 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-16 + +* Change default values of arrays from objects to actual arrays + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 490b2b6..8d66111 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -51,13 +51,13 @@ additionalArguments: [] + # - "--providers.kubernetesingress" + + # Secret to be set as environment variables to be passed to Traefik's binary +-secretEnv: {} ++secretEnv: [] + # - name: SOME_VAR + # secretName: my-secret-name + # secretKey: my-secret-key + + # Environment variables to be passed to Traefik's binary +-env: {} ++env: [] + # - name: SOME_VAR + # value: some-var-value + # - name: SOME_OTHER_VAR +@@ -109,7 +109,7 @@ service: + # externalTrafficPolicy: Cluster + # loadBalancerIP: "1.2.3.4" + # clusterIP: "2.3.4.5" +- loadBalancerSourceRanges: {} ++ loadBalancerSourceRanges: [] + # - 192.168.0.1/32 + # - 172.16.0.0/16 + +``` + +## 6.0.0 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-15 + +* Cleanup + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 7aebefe..490b2b6 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -18,15 +18,10 @@ ingressRoute: + dashboard: + enabled: true + +-additional: +- checkNewVersion: true +- sendAnonymousUsage: true +- + rollingUpdate: + maxUnavailable: 1 + maxSurge: 1 + +- + # + # Add volumes to the traefik pod. + # This can be used to mount a cert pair or a configmap that holds a config.toml file. +@@ -43,9 +38,14 @@ volumes: [] + # mountPath: "/config" + # type: configMap + ++globalArguments: ++ - "--global.checknewversion" ++ - "--global.sendanonymoususage" ++ + # +-# Configure Traefik entry points ++# Configure Traefik static configuration + # Additional arguments to be passed at Traefik's binary ++# All available options available on https://docs.traefik.io/reference/static-configuration/cli/ + ## Use curly braces to pass values: `helm install --set="additionalArguments={--providers.kubernetesingress,--global.checknewversion=true}"` + additionalArguments: [] + # - "--providers.kubernetesingress" +@@ -63,7 +63,7 @@ env: {} + # - name: SOME_OTHER_VAR + # value: some-other-var-value + +-# ++# Configure ports + ports: + # The name of this one can't be changed as it is used for the readiness and + # liveness probes, but you can adjust its config to your liking +@@ -94,7 +94,7 @@ ports: + # hostPort: 8443 + expose: true + exposedPort: 443 +- # nodePort: 32443 ++ # nodePort: 32443 + + # Options for the main traefik service, where the entrypoints traffic comes + # from. +@@ -113,9 +113,6 @@ service: + # - 192.168.0.1/32 + # - 172.16.0.0/16 + +-logs: +- loglevel: WARN +- + # Enable persistence using Persistent Volume Claims + # ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + # After the pvc has been mounted, add the configs into traefik by using the `additionalArguments` list below, eg: +``` + +## 5.6.0 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-12 + +* Add field enabled for resources + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 38bb263..7aebefe 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -7,11 +7,17 @@ image: + # Configure the deployment + # + deployment: ++ enabled: true + # Number of pods of the deployment + replicas: 1 + # Additional pod annotations (e.g. for mesh injection or prometheus scraping) + podAnnotations: {} + ++# Create an IngressRoute for the dashboard ++ingressRoute: ++ dashboard: ++ enabled: true ++ + additional: + checkNewVersion: true + sendAnonymousUsage: true +@@ -93,6 +99,7 @@ ports: + # Options for the main traefik service, where the entrypoints traffic comes + # from. + service: ++ enabled: true + type: LoadBalancer + # Additional annotations (e.g. for cloud provider specific config) + annotations: {} +``` + +## 5.5.0 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-12 + +* expose hostnetwork option + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index ecb2833..38bb263 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -123,6 +123,12 @@ persistence: + path: /data + annotations: {} + ++# If hostNetwork is true, runs traefik in the host network namespace ++# To prevent unschedulabel pods due to port collisions, if hostNetwork=true ++# and replicas>1, a pod anti-affinity is recommended and will be set if the ++# affinity is left as default. ++hostNetwork: false ++ + resources: {} + # requests: + # cpu: "100m" +@@ -131,5 +137,17 @@ resources: {} + # cpu: "300m" + # memory: "150Mi" + affinity: {} ++# # This example pod anti-affinity forces the scheduler to put traefik pods ++# # on nodes where no other traefik pods are scheduled. ++# # It should be used when hostNetwork: true to prevent port conflicts ++# podAntiAffinity: ++# requiredDuringSchedulingIgnoredDuringExecution: ++# - labelSelector: ++# matchExpressions: ++# - key: app ++# operator: In ++# values: ++# - {{ template "traefik.name" . }} ++# topologyKey: failure-domain.beta.kubernetes.io/zone + nodeSelector: {} + tolerations: [] +``` + +## 5.4.0 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-12 + +* Add support for hostport + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index ec1d619..ecb2833 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -63,6 +63,9 @@ ports: + # liveness probes, but you can adjust its config to your liking + traefik: + port: 9000 ++ # Use hostPort if set. ++ # hostPort: 9000 ++ + # Defines whether the port is exposed if service.type is LoadBalancer or + # NodePort. + # +@@ -74,6 +77,7 @@ ports: + exposedPort: 9000 + web: + port: 8000 ++ # hostPort: 8000 + expose: true + exposedPort: 80 + # Use nodeport if set. This is useful if you have configured Traefik in a +@@ -81,6 +85,7 @@ ports: + # nodePort: 32080 + websecure: + port: 8443 ++ # hostPort: 8443 + expose: true + exposedPort: 443 + # nodePort: 32443 +``` + +## 5.3.3 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-12 + +* Fix replica check + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 7f31548..ec1d619 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -40,7 +40,7 @@ volumes: [] + # + # Configure Traefik entry points + # Additional arguments to be passed at Traefik's binary +-## Use curly braces to pass values: `helm install --set="{--providers.kubernetesingress,--global.checknewversion=true}" ." ++## Use curly braces to pass values: `helm install --set="additionalArguments={--providers.kubernetesingress,--global.checknewversion=true}"` + additionalArguments: [] + # - "--providers.kubernetesingress" + +``` + +## 5.3.2 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-11 + +* Fixed typo in README + + +## 5.3.1 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-11 + +* Production ready + + +## 5.3.0 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-11 + +* Not authorise acme if replica > 1 + + +## 5.2.1 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-11 + +* Fix volume mount + + +## 5.2.0 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-11 + +* Add secret as env var + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index ccea845..7f31548 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -44,12 +44,18 @@ volumes: [] + additionalArguments: [] + # - "--providers.kubernetesingress" + ++# Secret to be set as environment variables to be passed to Traefik's binary ++secretEnv: {} ++ # - name: SOME_VAR ++ # secretName: my-secret-name ++ # secretKey: my-secret-key ++ + # Environment variables to be passed to Traefik's binary + env: {} +-# - name: SOME_VAR +-# value: some-var-value +-# - name: SOME_OTHER_VAR +-# value: some-other-var-value ++ # - name: SOME_VAR ++ # value: some-var-value ++ # - name: SOME_OTHER_VAR ++ # value: some-other-var-value + + # + ports: +``` + +## 5.1.0 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-10 + +* Enhance security by add loadBalancerSourceRanges to lockdown ip address. + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 78bbee0..ccea845 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -91,6 +91,9 @@ service: + # externalTrafficPolicy: Cluster + # loadBalancerIP: "1.2.3.4" + # clusterIP: "2.3.4.5" ++ loadBalancerSourceRanges: {} ++ # - 192.168.0.1/32 ++ # - 172.16.0.0/16 + + logs: + loglevel: WARN +``` + +## 5.0.0 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-10 + +* Expose dashboard by default but only on traefik entrypoint + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index a442fca..78bbee0 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -92,15 +92,6 @@ service: + # loadBalancerIP: "1.2.3.4" + # clusterIP: "2.3.4.5" + +-dashboard: +- # Enable the dashboard on Traefik +- enable: true +- +- # Expose the dashboard and api through an ingress route at /dashboard +- # and /api This is not secure and SHOULD NOT be enabled on production +- # deployments +- ingressRoute: false +- + logs: + loglevel: WARN + +``` + +## 4.1.3 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-10 + +* Add annotations for PVC (#98) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 8b2f4db..a442fca 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -116,6 +116,7 @@ persistence: + size: 128Mi + # storageClass: "" + path: /data ++ annotations: {} + + resources: {} + # requests: +``` + +## 4.1.2 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-10 + +* Added persistent volume support. (#86) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 2a2554f..8b2f4db 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -103,7 +103,20 @@ dashboard: + + logs: + loglevel: WARN +-# ++ ++# Enable persistence using Persistent Volume Claims ++# ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ ++# After the pvc has been mounted, add the configs into traefik by using the `additionalArguments` list below, eg: ++# additionalArguments: ++# - "--certificatesresolvers.le.acme.storage=/data/acme.json" ++# It will persist TLS certificates. ++persistence: ++ enabled: false ++ accessMode: ReadWriteOnce ++ size: 128Mi ++ # storageClass: "" ++ path: /data ++ + resources: {} + # requests: + # cpu: "100m" +``` + +## 4.1.1 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-10 + +* Add values to mount secrets or configmaps as volumes to the traefik pod (#84) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 5401832..2a2554f 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -20,6 +20,23 @@ rollingUpdate: + maxUnavailable: 1 + maxSurge: 1 + ++ ++# ++# Add volumes to the traefik pod. ++# This can be used to mount a cert pair or a configmap that holds a config.toml file. ++# After the volume has been mounted, add the configs into traefik by using the `additionalArguments` list below, eg: ++# additionalArguments: ++# - "--providers.file.filename=/config/dynamic.toml" ++# - "--tls.certificates.certFile=/certs/tls.crt" ++# - "--tls.certificates.keyFile=/certs/tls.key" ++volumes: [] ++# - name: public-cert ++# mountPath: "/certs" ++# type: secret ++# - name: configs ++# mountPath: "/config" ++# type: configMap ++ + # + # Configure Traefik entry points + # Additional arguments to be passed at Traefik's binary +``` + +## 4.1.0 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-10 + +* Add podAnnotations to the deployment (#83) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 5eab74b..5401832 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -9,6 +9,8 @@ image: + deployment: + # Number of pods of the deployment + replicas: 1 ++ # Additional pod annotations (e.g. for mesh injection or prometheus scraping) ++ podAnnotations: {} + + additional: + checkNewVersion: true +``` + +## 4.0.0 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-03-06 + +* Migrate to helm v3 (#94) + + +## 3.5.0 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-02-18 + +* Publish helm chart (#81) + + +## 3.4.0 ![AppVersion: 2.1.3](https://img.shields.io/static/v1?label=AppVersion&message=2.1.3&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-02-13 + +* fix: tests. +* feat: bump traefik to v2.1.3 +* Enable configuration of global checknewversion and sendanonymoususage (#80) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index bcc42f8..5eab74b 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -1,7 +1,7 @@ + # Default values for Traefik + image: + name: traefik +- tag: 2.1.1 ++ tag: 2.1.3 + + # + # Configure the deployment +@@ -10,6 +10,10 @@ deployment: + # Number of pods of the deployment + replicas: 1 + ++additional: ++ checkNewVersion: true ++ sendAnonymousUsage: true ++ + rollingUpdate: + maxUnavailable: 1 + maxSurge: 1 +``` + +## 3.3.3 ![AppVersion: 2.1.1](https://img.shields.io/static/v1?label=AppVersion&message=2.1.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-02-05 + +* fix: deployment environment variables. +* fix: chart version. + + +## 3.3.2 ![AppVersion: 2.1.1](https://img.shields.io/static/v1?label=AppVersion&message=2.1.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-02-03 + +* ix: deployment environment variables. + + +## 3.3.1 ![AppVersion: 2.1.1](https://img.shields.io/static/v1?label=AppVersion&message=2.1.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-01-27 + +* fix: deployment environment variables. + + +## 3.3.0 ![AppVersion: 2.1.1](https://img.shields.io/static/v1?label=AppVersion&message=2.1.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-01-24 + +* Enable configuration of environment variables in traefik deployment (#71) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index 4462359..bcc42f8 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -21,6 +21,13 @@ rollingUpdate: + additionalArguments: [] + # - "--providers.kubernetesingress" + ++# Environment variables to be passed to Traefik's binary ++env: {} ++# - name: SOME_VAR ++# value: some-var-value ++# - name: SOME_OTHER_VAR ++# value: some-other-var-value ++ + # + ports: + # The name of this one can't be changed as it is used for the readiness and +``` + +## 3.2.1 ![AppVersion: 2.1.1](https://img.shields.io/static/v1?label=AppVersion&message=2.1.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-01-22 + +* Add Unit Tests for the chart (#60) + + +## 3.2.0 ![AppVersion: 2.1.1](https://img.shields.io/static/v1?label=AppVersion&message=2.1.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-01-22 + +* Make NodePort configurable (#67) + +### Default value changes + +```diff +diff --git a/traefik/values.yaml b/traefik/values.yaml +index b1fe42a..4462359 100644 +--- a/traefik/values.yaml ++++ b/traefik/values.yaml +@@ -40,10 +40,14 @@ ports: + port: 8000 + expose: true + exposedPort: 80 ++ # Use nodeport if set. This is useful if you have configured Traefik in a ++ # LoadBalancer ++ # nodePort: 32080 + websecure: + port: 8443 + expose: true + exposedPort: 443 ++ # nodePort: 32443 + + # Options for the main traefik service, where the entrypoints traffic comes + # from. +``` + +## 3.1.0 ![AppVersion: 2.1.1](https://img.shields.io/static/v1?label=AppVersion&message=2.1.1&color=success&logo=) ![Helm: v2](https://img.shields.io/static/v1?label=Helm&message=v2&color=inactive&logo=helm) ![Helm: v3](https://img.shields.io/static/v1?label=Helm&message=v3&color=informational&logo=helm) + +**Release date:** 2020-01-20 + +* Switch Chart linting to ct (#59) + +### Default value changes + +```diff +# Default values for Traefik +image: + name: traefik + tag: 2.1.1 + +# +# Configure the deployment +# +deployment: + # Number of pods of the deployment + replicas: 1 + +rollingUpdate: + maxUnavailable: 1 + maxSurge: 1 + +# +# Configure Traefik entry points +# Additional arguments to be passed at Traefik's binary +## Use curly braces to pass values: `helm install --set="{--providers.kubernetesingress,--global.checknewversion=true}" ." +additionalArguments: [] +# - "--providers.kubernetesingress" + +# +ports: + # The name of this one can't be changed as it is used for the readiness and + # liveness probes, but you can adjust its config to your liking + traefik: + port: 9000 + # Defines whether the port is exposed if service.type is LoadBalancer or + # NodePort. + # + # You SHOULD NOT expose the traefik port on production deployments. + # If you want to access it from outside of your cluster, + # use `kubectl proxy` or create a secure ingress + expose: false + # The exposed port for this service + exposedPort: 9000 + web: + port: 8000 + expose: true + exposedPort: 80 + websecure: + port: 8443 + expose: true + exposedPort: 443 + +# Options for the main traefik service, where the entrypoints traffic comes +# from. +service: + type: LoadBalancer + # Additional annotations (e.g. for cloud provider specific config) + annotations: {} + # Additional entries here will be added to the service spec. Cannot contains + # type, selector or ports entries. + spec: {} + # externalTrafficPolicy: Cluster + # loadBalancerIP: "1.2.3.4" + # clusterIP: "2.3.4.5" + +dashboard: + # Enable the dashboard on Traefik + enable: true + + # Expose the dashboard and api through an ingress route at /dashboard + # and /api This is not secure and SHOULD NOT be enabled on production + # deployments + ingressRoute: false + +logs: + loglevel: WARN +# +resources: {} + # requests: + # cpu: "100m" + # memory: "50Mi" + # limits: + # cpu: "300m" + # memory: "150Mi" +affinity: {} +nodeSelector: {} +tolerations: [] +``` + +--- +Autogenerated from Helm Chart and git history using [helm-changelog](https://github.com/mogensen/helm-changelog) diff --git a/charts/traefik/traefik/30.0.2/Chart.yaml b/charts/traefik/traefik/30.0.2/Chart.yaml new file mode 100644 index 000000000..5ec014fe9 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/Chart.yaml @@ -0,0 +1,31 @@ +annotations: + artifacthub.io/changes: "- \"fix(Traefik Hub): missing RBACs for Traefik Hub\"\n- + \"chore(release): \U0001F680 publish v30.0.2\"\n" + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: Traefik Proxy + catalog.cattle.io/kube-version: '>=1.22.0-0' + catalog.cattle.io/release-name: traefik +apiVersion: v2 +appVersion: v3.1.0 +description: A Traefik based Kubernetes ingress controller +home: https://traefik.io/ +icon: file://assets/icons/traefik.png +keywords: +- traefik +- ingress +- networking +kubeVersion: '>=1.22.0-0' +maintainers: +- email: michel.loiseleur@traefik.io + name: mloiseleur +- email: charlie.haley@traefik.io + name: charlie-haley +- email: remi.buisson@traefik.io + name: darkweaver87 +- name: jnoordsij +name: traefik +sources: +- https://github.com/traefik/traefik +- https://github.com/traefik/traefik-helm-chart +type: application +version: 30.0.2 diff --git a/charts/traefik/traefik/30.0.2/EXAMPLES.md b/charts/traefik/traefik/30.0.2/EXAMPLES.md new file mode 100644 index 000000000..ee9816824 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/EXAMPLES.md @@ -0,0 +1,994 @@ +# Install as a DaemonSet + +Default install is using a `Deployment` but it's possible to use `DaemonSet` + +```yaml +deployment: + kind: DaemonSet +``` + +# Configure traefik Pod parameters + +## Extending /etc/hosts records + +In some specific cases, you'll need to add extra records to the `/etc/hosts` file for the Traefik containers. +You can configure it using [hostAliases](https://kubernetes.io/docs/tasks/network/customize-hosts-file-for-pods/): + +```yaml +deployment: + hostAliases: + - ip: "127.0.0.1" # this is an example + hostnames: + - "foo.local" + - "bar.local" +``` +## Extending DNS config + +In order to configure additional DNS servers for your traefik pod, you can use `dnsConfig` option: + +```yaml +deployment: + dnsConfig: + nameservers: + - 192.0.2.1 # this is an example + searches: + - ns1.svc.cluster-domain.example + - my.dns.search.suffix + options: + - name: ndots + value: "2" + - name: edns0 +``` + +# Install in a dedicated namespace, with limited RBAC + +Default install is using Cluster-wide RBAC but it can be restricted to target namespace. + +```yaml +rbac: + namespaced: true +``` + +# Install with auto-scaling + +When enabling [HPA](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) +to adjust replicas count according to CPU Usage, you'll need to set resources and nullify replicas. + +```yaml +deployment: + replicas: null +resources: + requests: + cpu: "100m" + memory: "50Mi" + limits: + cpu: "300m" + memory: "150Mi" +autoscaling: + enabled: true + maxReplicas: 2 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80 +``` + +# Access Traefik dashboard without exposing it + +This Chart does not expose the Traefik local dashboard by default. It's explained in upstream [documentation](https://doc.traefik.io/traefik/operations/api/) why: + +> Enabling the API in production is not recommended, because it will expose all configuration elements, including sensitive data. + +It says also: + +> In production, it should be at least secured by authentication and authorizations. + +Thus, there are multiple ways to expose the dashboard. For instance, after enabling the creation of dashboard `IngressRoute` in the values: + +```yaml +ingressRoute: + dashboard: + enabled: true +``` + +The traefik admin port can be forwarded locally: + +```bash +kubectl port-forward $(kubectl get pods --selector "app.kubernetes.io/name=traefik" --output=name) 9000:9000 +``` + +This command makes the dashboard accessible on the url: http://127.0.0.1:9000/dashboard/ + +# Publish and protect Traefik Dashboard with basic Auth + +To expose the dashboard in a secure way as [recommended](https://doc.traefik.io/traefik/operations/dashboard/#dashboard-router-rule) +in the documentation, it may be useful to override the router rule to specify +a domain to match, or accept requests on the root path (/) in order to redirect +them to /dashboard/. + +```yaml +# Create an IngressRoute for the dashboard +ingressRoute: + dashboard: + enabled: true + # Custom match rule with host domain + matchRule: Host(`traefik-dashboard.example.com`) + entryPoints: ["websecure"] + # Add custom middlewares : authentication and redirection + middlewares: + - name: traefik-dashboard-auth + +# Create the custom middlewares used by the IngressRoute dashboard (can also be created in another way). +# /!\ Yes, you need to replace "changeme" password with a better one. /!\ +extraObjects: + - apiVersion: v1 + kind: Secret + metadata: + name: traefik-dashboard-auth-secret + type: kubernetes.io/basic-auth + stringData: + username: admin + password: changeme + + - apiVersion: traefik.io/v1alpha1 + kind: Middleware + metadata: + name: traefik-dashboard-auth + spec: + basicAuth: + secret: traefik-dashboard-auth-secret +``` + +# Publish and protect Traefik Dashboard with an Ingress + +To expose the dashboard without IngressRoute, it's more complicated and less +secure. You'll need to create an internal Service exposing Traefik API with +special _traefik_ entrypoint. This internal Service can be created from an other tool, with the `extraObjects` section or using [custom services](#add-custom-internal-services). + +You'll need to double check: +1. Service selector with your setup. +2. Middleware annotation on the ingress, _default_ should be replaced with traefik's namespace + +```yaml +ingressRoute: + dashboard: + enabled: false +additionalArguments: +- "--api.insecure=true" +# Create the service, middleware and Ingress used to expose the dashboard (can also be created in another way). +# /!\ Yes, you need to replace "changeme" password with a better one. /!\ +extraObjects: + - apiVersion: v1 + kind: Service + metadata: + name: traefik-api + spec: + type: ClusterIP + selector: + app.kubernetes.io/name: traefik + app.kubernetes.io/instance: traefik-default + ports: + - port: 8080 + name: traefik + targetPort: 9000 + protocol: TCP + + - apiVersion: v1 + kind: Secret + metadata: + name: traefik-dashboard-auth-secret + type: kubernetes.io/basic-auth + stringData: + username: admin + password: changeme + + - apiVersion: traefik.io/v1alpha1 + kind: Middleware + metadata: + name: traefik-dashboard-auth + spec: + basicAuth: + secret: traefik-dashboard-auth-secret + + - apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + name: traefik-dashboard + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.middlewares: default-traefik-dashboard-auth@kubernetescrd + spec: + rules: + - host: traefik-dashboard.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: traefik-api + port: + name: traefik +``` + + +# Install on AWS + +It can use [native AWS support](https://kubernetes.io/docs/concepts/services-networking/service/#aws-nlb-support) on Kubernetes + +```yaml +service: + annotations: + service.beta.kubernetes.io/aws-load-balancer-type: nlb +``` + +Or if [AWS LB controller](https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.2/guide/service/annotations/#legacy-cloud-provider) is installed : +```yaml +service: + annotations: + service.beta.kubernetes.io/aws-load-balancer-type: nlb-ip +``` + +# Install on GCP + +A [regional IP with a Service](https://cloud.google.com/kubernetes-engine/docs/tutorials/configuring-domain-name-static-ip#use_a_service) can be used +```yaml +service: + spec: + loadBalancerIP: "1.2.3.4" +``` + +Or a [global IP on Ingress](https://cloud.google.com/kubernetes-engine/docs/tutorials/configuring-domain-name-static-ip#use_an_ingress) +```yaml +service: + type: NodePort +extraObjects: + - apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + name: traefik + annotations: + kubernetes.io/ingress.global-static-ip-name: "myGlobalIpName" + spec: + defaultBackend: + service: + name: traefik + port: + number: 80 +``` + +Or a [global IP on a Gateway](https://cloud.google.com/kubernetes-engine/docs/how-to/deploying-gateways) with continuous HTTPS encryption. + +```yaml +ports: + websecure: + appProtocol: HTTPS # Hint for Google L7 load balancer +service: + type: ClusterIP +extraObjects: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + name: traefik + annotations: + networking.gke.io/certmap: "myCertificateMap" + spec: + gatewayClassName: gke-l7-global-external-managed + addresses: + - type: NamedAddress + value: "myGlobalIPName" + listeners: + - name: https + protocol: HTTPS + port: 443 +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: HTTPRoute + metadata: + name: traefik + spec: + parentRefs: + - kind: Gateway + name: traefik + rules: + - backendRefs: + - name: traefik + port: 443 +- apiVersion: networking.gke.io/v1 + kind: HealthCheckPolicy + metadata: + name: traefik + spec: + default: + config: + type: HTTP + httpHealthCheck: + port: 9000 + requestPath: /ping + targetRef: + group: "" + kind: Service + name: traefik +``` + +# Install on Azure + +A [static IP on a resource group](https://learn.microsoft.com/en-us/azure/aks/static-ip) can be used: + +```yaml +service: + spec: + loadBalancerIP: "1.2.3.4" + annotations: + service.beta.kubernetes.io/azure-load-balancer-resource-group: myResourceGroup +``` + +Here is a more complete example, using also native Let's encrypt feature of Traefik Proxy with Azure DNS: + +```yaml +persistence: + enabled: true + size: 128Mi +certResolvers: + letsencrypt: + email: "{{ letsencrypt_email }}" + #caServer: https://acme-v02.api.letsencrypt.org/directory # Production server + caServer: https://acme-staging-v02.api.letsencrypt.org/directory # Staging server + dnsChallenge: + provider: azuredns + storage: /data/acme.json +env: + - name: AZURE_CLIENT_ID + value: "{{ azure_dns_challenge_application_id }}" + - name: AZURE_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: azuredns-secret + key: client-secret + - name: AZURE_SUBSCRIPTION_ID + value: "{{ azure_subscription_id }}" + - name: AZURE_TENANT_ID + value: "{{ azure_tenant_id }}" + - name: AZURE_RESOURCE_GROUP + value: "{{ azure_resource_group }}" +deployment: + initContainers: + - name: volume-permissions + image: busybox:latest + command: ["sh", "-c", "ls -la /; touch /data/acme.json; chmod -v 600 /data/acme.json"] + volumeMounts: + - mountPath: /data + name: data +podSecurityContext: + fsGroup: 65532 + fsGroupChangePolicy: "OnRootMismatch" +service: + spec: + type: LoadBalancer + annotations: + service.beta.kubernetes.io/azure-load-balancer-resource-group: "{{ azure_node_resource_group }}" + service.beta.kubernetes.io/azure-pip-name: "{{ azure_resource_group }}" + service.beta.kubernetes.io/azure-dns-label-name: "{{ azure_resource_group }}" + service.beta.kubernetes.io/azure-allowed-ip-ranges: "{{ ip_range | join(',') }}" +extraObjects: + - apiVersion: v1 + kind: Secret + metadata: + name: azuredns-secret + namespace: traefik + type: Opaque + stringData: + client-secret: "{{ azure_dns_challenge_application_secret }}" +``` + +# Use an IngressClass + +Default install comes with an `IngressClass` resource that can be enabled on providers. + +Here's how one can enable it on CRD & Ingress Kubernetes provider: + +```yaml +ingressClass: + name: traefik +providers: + kubernetesCRD: + ingressClass: traefik + kubernetesIngress: + ingressClass: traefik +``` + +# Use HTTP3 + +By default, it will use a Load balancers with mixed protocols on `websecure` +entrypoint. They are available since v1.20 and in beta as of Kubernetes v1.24. +Availability may depend on your Kubernetes provider. + +When using TCP and UDP with a single service, you may encounter [this issue](https://github.com/kubernetes/kubernetes/issues/47249#issuecomment-587960741) from Kubernetes. +If you want to avoid this issue, you can set `ports.websecure.http3.advertisedPort` +to an other value than 443 + +```yaml +ports: + websecure: + http3: + enabled: true +``` + +# Use PROXY protocol on Digital Ocean + +PROXY protocol is a protocol for sending client connection information, such as origin IP addresses and port numbers, to the final backend server, rather than discarding it at the load balancer. + +```yaml +.DOTrustedIPs: &DOTrustedIPs + - 127.0.0.1/32 + - 10.120.0.0/16 + +service: + enabled: true + type: LoadBalancer + annotations: + # This will tell DigitalOcean to enable the proxy protocol. + service.beta.kubernetes.io/do-loadbalancer-enable-proxy-protocol: "true" + spec: + # This is the default and should stay as cluster to keep the DO health checks working. + externalTrafficPolicy: Cluster + +ports: + web: + forwardedHeaders: + trustedIPs: *DOTrustedIPs + proxyProtocol: + trustedIPs: *DOTrustedIPs + websecure: + forwardedHeaders: + trustedIPs: *DOTrustedIPs + proxyProtocol: + trustedIPs: *DOTrustedIPs +``` + +# Enable plugin storage + +This chart follows common security practices: it runs as non root with a readonly root filesystem. +When enabling a plugin which needs storage, you have to add it to the deployment. + +Here is a simple example with crowdsec. You may want to replace with your plugin or see complete exemple on crowdsec [here](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/examples/kubernetes/README.md). + +```yaml +deployment: + additionalVolumes: + - name: plugins +additionalVolumeMounts: +- name: plugins + mountPath: /plugins-storage +additionalArguments: +- "--experimental.plugins.bouncer.moduleName=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" +- "--experimental.plugins.bouncer.version=v1.1.9" +``` + +# Use Traefik native Let's Encrypt integration, without cert-manager + +In Traefik Proxy, ACME certificates are stored in a JSON file. + +This file needs to have 0600 permissions, meaning, only the owner of the file has full read and write access to it. +By default, Kubernetes recursively changes ownership and permissions for the content of each volume. + +=> An initContainer can be used to avoid an issue on this sensitive file. +See [#396](https://github.com/traefik/traefik-helm-chart/issues/396) for more details. + +Once the provider is ready, it can be used in an `IngressRoute`: + +```yaml +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: [...] +spec: + entryPoints: [...] + routes: [...] + tls: + certResolver: letsencrypt +``` + +See [the list of supported providers](https://doc.traefik.io/traefik/https/acme/#providers) for others. + +## Example with CloudFlare + +This example needs a CloudFlare token in a Kubernetes `Secret` and a working `StorageClass`. + +**Step 1**: Create `Secret` with CloudFlare token: + +```yaml +--- +apiVersion: v1 +kind: Secret +metadata: + name: cloudflare +type: Opaque +stringData: + token: {{ SET_A_VALID_TOKEN_HERE }} +``` + +**Step 2**: + +```yaml +persistence: + enabled: true + storageClass: xxx +certResolvers: + letsencrypt: + dnsChallenge: + provider: cloudflare + storage: /data/acme.json +env: + - name: CF_DNS_API_TOKEN + valueFrom: + secretKeyRef: + name: cloudflare + key: token +deployment: + initContainers: + - name: volume-permissions + image: busybox:latest + command: ["sh", "-c", "touch /data/acme.json; chmod -v 600 /data/acme.json"] + volumeMounts: + - mountPath: /data + name: data +podSecurityContext: + fsGroup: 65532 + fsGroupChangePolicy: "OnRootMismatch" +``` + +# Provide default certificate with cert-manager and CloudFlare DNS + +Setup: + +* cert-manager installed in `cert-manager` namespace +* A cloudflare account on a DNS Zone + +**Step 1**: Create `Secret` and `Issuer` needed by `cert-manager` with your API Token. +See [cert-manager documentation](https://cert-manager.io/docs/configuration/acme/dns01/cloudflare/) +for creating this token with needed rights: + +```yaml +--- +apiVersion: v1 +kind: Secret +metadata: + name: cloudflare + namespace: traefik +type: Opaque +stringData: + api-token: XXX +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: cloudflare + namespace: traefik +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: email@example.com + privateKeySecretRef: + name: cloudflare-key + solvers: + - dns01: + cloudflare: + apiTokenSecretRef: + name: cloudflare + key: api-token +``` + +**Step 2**: Create `Certificate` in traefik namespace + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: wildcard-example-com + namespace: traefik +spec: + secretName: wildcard-example-com-tls + dnsNames: + - "example.com" + - "*.example.com" + issuerRef: + name: cloudflare + kind: Issuer +``` + +**Step 3**: Check that it's ready + +```bash +kubectl get certificate -n traefik +``` + +If needed, logs of cert-manager pod can give you more information + +**Step 4**: Use it on the TLS Store in **values.yaml** file for this Helm Chart + +```yaml +tlsStore: + default: + defaultCertificate: + secretName: wildcard-example-com-tls +``` + +**Step 5**: Enjoy. All your `IngressRoute` use this certificate by default now. + +They should use websecure entrypoint like this: + +```yaml +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: example-com-tls +spec: + entryPoints: + - websecure + routes: + - match: Host(`test.example.com`) + kind: Rule + services: + - name: XXXX + port: 80 +``` + +# Add custom (internal) services + +In some cases you might want to have more than one Traefik service within your cluster, +e.g. a default (external) one and a service that is only exposed internally to pods within your cluster. + +The `service.additionalServices` allows you to add an arbitrary amount of services, +provided as a name to service details mapping; for example you can use the following values: + +```yaml +service: + additionalServices: + internal: + type: ClusterIP + labels: + traefik-service-label: internal +``` + +Ports can then be exposed on this service by using the port name to boolean mapping `expose` on the respective port; +e.g. to expose the `traefik` API port on your internal service so pods within your cluster can use it, you can do: + +```yaml +ports: + traefik: + expose: + # Sensitive data should not be exposed on the internet + # => Keep this disabled ! + default: false + internal: true +``` + +This will then provide an additional Service manifest, looking like this: + +```yaml +--- +# Source: traefik/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: traefik-internal + namespace: traefik +[...] +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: traefik + app.kubernetes.io/instance: traefik-traefik + ports: + - port: 9000 + name: "traefik" + targetPort: traefik + protocol: TCP +``` + +# Use this Chart as a dependency of your own chart + + +First, let's create a default Helm Chart, with Traefik as a dependency. +```bash +helm create foo +cd foo +echo " +dependencies: + - name: traefik + version: "24.0.0" + repository: "https://traefik.github.io/charts" +" >> Chart.yaml +``` + +Second, let's tune some values like enabling HPA: + +```bash +cat <<-EOF >> values.yaml +traefik: + autoscaling: + enabled: true + maxReplicas: 3 +EOF +``` + +Third, one can see if it works as expected: +```bash +helm dependency update +helm dependency build +helm template . | grep -A 14 -B 3 Horizontal +``` + +It should produce this output: + +```yaml +--- +# Source: foo/charts/traefik/templates/hpa.yaml +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: release-name-traefik + namespace: flux-system + labels: + app.kubernetes.io/name: traefik + app.kubernetes.io/instance: release-name-flux-system + helm.sh/chart: traefik-24.0.0 + app.kubernetes.io/managed-by: Helm +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: release-name-traefik + maxReplicas: 3 +``` + +# Configure TLS + +The [TLS options](https://doc.traefik.io/traefik/https/tls/#tls-options) allow one to configure some parameters of the TLS connection. + +```yaml +tlsOptions: + default: + labels: {} + sniStrict: true + custom-options: + labels: {} + curvePreferences: + - CurveP521 + - CurveP384 +``` + +# Use latest build of Traefik v3 from master + +An experimental build of Traefik Proxy is available on a specific repository. + +It can be used with those _values_: + +```yaml +image: + repository: traefik/traefik + tag: experimental-v3.0 +``` + +# Use Prometheus Operator + +An optional support of this operator is included in this Chart. See documentation of this operator for more details. + +It can be used with those _values_: + +```yaml +metrics: + prometheus: + service: + enabled: true + disableAPICheck: false + serviceMonitor: + enabled: true + metricRelabelings: + - sourceLabels: [__name__] + separator: ; + regex: ^fluentd_output_status_buffer_(oldest|newest)_.+ + replacement: $1 + action: drop + relabelings: + - sourceLabels: [__meta_kubernetes_pod_node_name] + separator: ; + regex: ^(.*)$ + targetLabel: nodename + replacement: $1 + action: replace + jobLabel: traefik + interval: 30s + honorLabels: true + prometheusRule: + enabled: true + rules: + - alert: TraefikDown + expr: up{job="traefik"} == 0 + for: 5m + labels: + context: traefik + severity: warning + annotations: + summary: "Traefik Down" + description: "{{ $labels.pod }} on {{ $labels.nodename }} is down" +``` + +# Use kubernetes Gateway API + +One can use the new stable kubernetes gateway API provider setting the following _values_: + +```yaml +image: + tag: v3.1.0-rc3 +providers: + kubernetesGateway: + enabled: true +``` + +

    + +With those values, a whoami service can be exposed with a HTTPRoute + +```yaml +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: whoami +spec: + replicas: 2 + selector: + matchLabels: + app: whoami + template: + metadata: + labels: + app: whoami + spec: + containers: + - name: whoami + image: traefik/whoami + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami +spec: + selector: + app: whoami + ports: + - protocol: TCP + port: 80 + +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: whoami +spec: + parentRefs: + - name: traefik-gateway + hostnames: + - whoami.docker.localhost + rules: + - matches: + - path: + type: Exact + value: / + + backendRefs: + - name: whoami + port: 80 + weight: 1 +``` + +Once it's applied, whoami should be accessible on http://whoami.docker.localhost/ + +
    + +# Use Kubernetes Gateway API with cert-manager + +One can use the new stable kubernetes gateway API provider with automatic TLS certificates delivery (with cert-manager) setting the following _values_: + +```yaml +providers: + kubernetesGateway: + enabled: true +gateway: + enabled: true + annotations: + cert-manager.io/issuer: selfsigned-issuer + listeners: + websecure: + hostname: whoami.docker.localhost + certificateRefs: + - name: whoami-tls +``` + +Install cert-manager: + +```bash +helm repo add jetstack https://charts.jetstack.io --force-update +helm upgrade --install \ +cert-manager jetstack/cert-manager \ +--namespace cert-manager \ +--create-namespace \ +--version v1.15.1 \ +--set crds.enabled=true \ +--set "extraArgs={--enable-gateway-api}" +``` + +
    + +With those values, a whoami service can be exposed with HTTPRoute on both HTTP and HTTPS + +```yaml +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: whoami +spec: + replicas: 2 + selector: + matchLabels: + app: whoami + template: + metadata: + labels: + app: whoami + spec: + containers: + - name: whoami + image: traefik/whoami + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami +spec: + selector: + app: whoami + ports: + - protocol: TCP + port: 80 + +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: whoami +spec: + parentRefs: + - name: traefik-gateway + hostnames: + - whoami.docker.localhost + rules: + - matches: + - path: + type: Exact + value: / + + backendRefs: + - name: whoami + port: 80 + weight: 1 + +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: selfsigned-issuer +spec: + selfSigned: {} +``` + +Once it's applied, whoami should be accessible on https://whoami.docker.localhost/ + +
    diff --git a/charts/traefik/traefik/30.0.2/Guidelines.md b/charts/traefik/traefik/30.0.2/Guidelines.md new file mode 100644 index 000000000..19937d4da --- /dev/null +++ b/charts/traefik/traefik/30.0.2/Guidelines.md @@ -0,0 +1,92 @@ +# Traefik Helm Chart Guidelines + +This document outlines the guidelines for developing, managing and extending the Traefik helm chart. + +This Helm Chart is documented using field description from comments with [helm-docs](https://github.com/norwoodj/helm-docs). + +Optionality +All non-critical features (Features not mandatory to starting Traefik) in the helm chart must be optional. All non-critical features should be disabled (commented out) in the values.yaml file. All optional non-critical features should be disabled (commented out) in the values.yaml file, and have a comment # (Optional) in the line above. This allows minimal configuration, and ease of extension. + +## Feature Example + +```yaml +image: + # -- Traefik image host registry + registry: docker.io +``` + +This feature is expected and therefore is defined clearly in the values.yaml file. + +## Optional Feature Example + +```yaml +# storage: +# controlNode: +# type: emptyDir +``` + +This feature is optional, non-critical, and therefore is commented out by default in the values.yaml file. + +To allow this, template blocks that use this need to recursively test for existence of values before using them: + +```yaml +{{- if .Values.storage}} + {{- if .Values.storage.controlNode }} + //code + {{ .Values.storage.controlNode.type }} + {{- end }} +{{- end }} +``` + +The non-critical feature defaults should be populated so that they can be enabled by simply uncommenting the section in the values.yaml file. + +## Optional Non-Critical Feature Example + +```yaml +# storage: +# controlNode: +# type: emptyDir +# # (Optional) +# # volume: 1Gi +``` + +The volume option is clearly optional, and non-critical. It is commented out (apart from the storage section comment block), and is also preceded by a comment of # (Optional) in the preceding line. This facilitates configuration, when the storage section is uncommented, the optional features are still disabled by default. + +Similar to non-critical features, these options need to be tested for existence before use in the template. + +Note +There can be optional values in critical features. These should just be added as an uncommented non-critical feature: + +```yaml +image: + name: traefik + tag: 2.0.0 + # (Optional) + # pullPolicy: IfNotPresent +``` + +Also, the first value under the primary value key does not require an optional comment: + +```yaml +# ports: +# http: 80 +# # (Optional) +# # https: 443 +``` + +This is because if the main subkey is not defined, the entirety of the feature is optional. + +## Whitespace + +Extra whitespace is to be avoided in templating. Conditionals should chomp whitespace: + +```yaml +{{- if .Values }} +{{- end }} +``` + +There should be an empty commented line between each primary key in the values.yaml file to separate features from each other. + +## Values YAML Design + +The values.yaml file is designed to be user-friendly. It does not have to resemble the templated configuration if it is not conducive. Similarly, value names to not have to correspond to fields in the template if it is not conducive. diff --git a/charts/traefik/traefik/30.0.2/LICENSE b/charts/traefik/traefik/30.0.2/LICENSE new file mode 100644 index 000000000..907ff8321 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/LICENSE @@ -0,0 +1,202 @@ + 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 2020 Containous + Copyright 2020 Traefik Labs + + 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/traefik/traefik/30.0.2/README.md b/charts/traefik/traefik/30.0.2/README.md new file mode 100644 index 000000000..cd963c199 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/README.md @@ -0,0 +1,158 @@ +# Traefik + +[Traefik](https://traefik.io/) is a modern HTTP reverse proxy and load balancer made to deploy +microservices with ease. + +## Introduction + +Starting with v28.x, this chart now bootstraps Traefik Proxy version 3 as a Kubernetes ingress controller, +using Custom Resources `IngressRoute`: . + +It's possible to use this chart with Traefik Proxy v2 using v27.x +This chart support policy is aligned with [upstream support policy](https://doc.traefik.io/traefik/deprecation/releases/) of Traefik Proxy. + +See [Migration guide from v2 to v3](https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/) and upgrading section of this chart on CRDs. + +### Philosophy + +The Traefik HelmChart is focused on Traefik deployment configuration. + +To keep this HelmChart as generic as possible we tend +to avoid integrating any third party solutions nor any specific use cases. + +Accordingly, the encouraged approach to fulfill your needs: + +1. Override the default Traefik configuration values ([yaml file or cli](https://helm.sh/docs/chart_template_guide/values_files/)) +2. Append your own configurations (`kubectl apply -f myconf.yaml`) + +[Examples](https://github.com/traefik/traefik-helm-chart/blob/master/EXAMPLES.md) of common usage are provided. + +If needed, one may use [extraObjects](./traefik/tests/values/extra.yaml) or extend this HelmChart [as a Subchart](https://helm.sh/docs/chart_template_guide/subcharts_and_globals/). + +## Installing + +### Prerequisites + +1. [x] Helm **v3 > 3.9.0** [installed](https://helm.sh/docs/using_helm/#installing-helm): `helm version` +2. [x] Traefik's chart repository: `helm repo add traefik https://traefik.github.io/charts` + +### Kubernetes Version Support + +Due to changes in CRD version support, the following versions of the chart are usable and supported on the following Kubernetes versions: + +| | Kubernetes v1.15 and below | Kubernetes v1.16-v1.21 | Kubernetes v1.22 and above | +|-------------------------|-----------------------------|------------------------|----------------------------| +| Chart v9.20.2 and below | [x] | [x] | | +| Chart v10.0.0 and above | | [x] | [x] | +| Chart v22.0.0 and above | | | [x] | + +### CRDs Support of Traefik Proxy + +Due to changes in API Group of Traefik CRDs from `containo.us` to `traefik.io`, this Chart install CRDs needed by default Traefik Proxy version, following this table: + +| | `containo.us` | `traefik.io` | +|-------------------------|-----------------------------|------------------------| +| Chart v22.0.0 and below | [x] | | +| Chart v23.0.0 and above | [x] | [x] | +| Chart v28.0.0 and above | | [x] | + +### Deploying Traefik + +```bash +helm install traefik traefik/traefik +``` + +or: + +```bash +helm install traefik oci://ghcr.io/traefik/helm/traefik +``` + +You can customize the install with a `values` file. There are some [EXAMPLES](./EXAMPLES.md) provided. +Complete documentation on all available parameters is in the [default file](./traefik/values.yaml). + +```bash +helm install -f myvalues.yaml traefik traefik/traefik +``` + +🛂 **Warning**: Helm v2 support was removed in the chart version 10.0.0. + +## Upgrading + +One can check what has changed in the [Changelog](./traefik/Changelog.md). + +:information_source: With Helm v3, CRDs created by this chart can not be updated, cf. the [Helm Documentation on CRDs](https://helm.sh/docs/chart_best_practices/custom_resource_definitions). + +:warning: Please read carefully release notes of this chart before upgrading CRDs. + +```bash +# Update repository +helm repo update +# See current Chart & Traefik version +helm search repo traefik/traefik +# Update CRDs (Traefik Proxy v3 CRDs) +kubectl apply --server-side --force-conflicts -k https://github.com/traefik/traefik-helm-chart/traefik/crds/ +# Upgrade Traefik +helm upgrade traefik traefik/traefik +``` + +New major version indicates that there is an incompatible breaking change. + +#### Upgrade up to 27.X + +When upgrading on Traefik Proxy v2 version, one need to stay at Traefik Helm Chart v27.x. The command to upgrade to the latest Traefik Proxy v2 CRD is: + +```bash +kubectl apply --server-side --force-conflicts -k https://github.com/traefik/traefik-helm-chart/traefik/crds/?ref=v27 +``` + +### Upgrading after 18.X+ + +It's detailed in [release notes](https://github.com/traefik/traefik-helm-chart/releases). + +### Upgrading from 17.x to 18.x + +Since v18.x, this chart by default merges TCP and UDP ports into a single (LoadBalancer) `Service`. +Load balancers with mixed protocols are available since v1.20 and in +[beta as of Kubernetes v1.24](https://kubernetes.io/docs/concepts/services-networking/service/#load-balancers-with-mixed-protocol-types). +Availability may depend on your Kubernetes provider. + +To retain the old default behavior, set `service.single` to `false` in your values. + +When using TCP and UDP with a single service, you may encounter +[this issue](https://github.com/kubernetes/kubernetes/issues/47249#issuecomment-587960741) +from Kubernetes. + +On HTTP/3, if you want to avoid this issue, you can set +`ports.websecure.http3.advertisedPort` to an other value than `443` + +If you were previously using HTTP/3, you should update your values as follows: + - Replace the old value (`true`) of `ports.websecure.http3` with a key `enabled: true` + - Remove `experimental.http3.enabled=true` entry + +### Upgrading from 16.x to 17.x + +Since v17.x, this chart provides unified labels following +[Kubernetes recommendation](https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/). + +This version needs to change an immutable field, which is not supported by +Kubernetes and Helm, see [this issue](https://github.com/helm/helm/issues/7350) +for more details. +So you will have to delete your `Service`, `Deployment` or `DaemonSet` in +order to be able to upgrade. + +You may also upgrade by deploying another Traefik to a different namespace and +removing after your first Traefik. + +Alternatively, since version 20.3.0 of this chart, you may set `instanceLabelOverride` to the previous value of that label. +This will override the new `Release.Name-Release.Namespace` pattern to avoid any (longer) downtime. + +## Contributing + +If you want to contribute to this chart, please read the [Contributing Guide](./CONTRIBUTING.md). + +Thanks to all the people who have already contributed! + + + + diff --git a/charts/traefik/traefik/30.0.2/VALUES.md b/charts/traefik/traefik/30.0.2/VALUES.md new file mode 100644 index 000000000..1ca3baf22 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/VALUES.md @@ -0,0 +1,284 @@ +# traefik + +![Version: 30.0.2](https://img.shields.io/badge/Version-30.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v3.1.0](https://img.shields.io/badge/AppVersion-v3.1.0-informational?style=flat-square) + +A Traefik based Kubernetes ingress controller + +**Homepage:** + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| mloiseleur | | | +| charlie-haley | | | +| darkweaver87 | | | +| jnoordsij | | | + +## Source Code + +* +* + +## Requirements + +Kubernetes: `>=1.22.0-0` + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| additionalArguments | list | `[]` | Additional arguments to be passed at Traefik's binary See [CLI Reference](https://docs.traefik.io/reference/static-configuration/cli/) Use curly braces to pass values: `helm install --set="additionalArguments={--providers.kubernetesingress.ingressclass=traefik-internal,--log.level=DEBUG}"` | +| additionalVolumeMounts | list | `[]` | Additional volumeMounts to add to the Traefik container | +| affinity | object | `{}` | on nodes where no other traefik pods are scheduled. It should be used when hostNetwork: true to prevent port conflicts | +| autoscaling.enabled | bool | `false` | Create HorizontalPodAutoscaler object. See EXAMPLES.md for more details. | +| certResolvers | object | `{}` | Certificates resolvers configuration. Ref: https://doc.traefik.io/traefik/https/acme/#certificate-resolvers See EXAMPLES.md for more details. | +| commonLabels | object | `{}` | Add additional label to all resources | +| core.defaultRuleSyntax | string | `nil` | Can be used to use globally v2 router syntax See https://doc.traefik.io/traefik/v3.0/migration/v2-to-v3/#new-v3-syntax-notable-changes | +| deployment.additionalContainers | list | `[]` | Additional containers (e.g. for metric offloading sidecars) | +| deployment.additionalVolumes | list | `[]` | Additional volumes available for use with initContainers and additionalContainers | +| deployment.annotations | object | `{}` | Additional deployment annotations (e.g. for jaeger-operator sidecar injection) | +| deployment.dnsConfig | object | `{}` | Custom pod [DNS config](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#poddnsconfig-v1-core) | +| deployment.enabled | bool | `true` | Enable deployment | +| deployment.hostAliases | list | `[]` | Custom [host aliases](https://kubernetes.io/docs/tasks/network/customize-hosts-file-for-pods/) | +| deployment.imagePullSecrets | list | `[]` | Pull secret for fetching traefik container image | +| deployment.initContainers | list | `[]` | Additional initContainers (e.g. for setting file permission as shown below) | +| deployment.kind | string | `"Deployment"` | Deployment or DaemonSet | +| deployment.labels | object | `{}` | Additional deployment labels (e.g. for filtering deployment by custom labels) | +| deployment.lifecycle | object | `{}` | Pod lifecycle actions | +| deployment.minReadySeconds | int | `0` | The minimum number of seconds Traefik needs to be up and running before the DaemonSet/Deployment controller considers it available | +| deployment.podAnnotations | object | `{}` | Additional pod annotations (e.g. for mesh injection or prometheus scraping) It supports templating. One can set it with values like traefik/name: '{{ template "traefik.name" . }}' | +| deployment.podLabels | object | `{}` | Additional Pod labels (e.g. for filtering Pod by custom labels) | +| deployment.replicas | int | `1` | Number of pods of the deployment (only applies when kind == Deployment) | +| deployment.runtimeClassName | string | `nil` | Set a runtimeClassName on pod | +| deployment.shareProcessNamespace | bool | `false` | Use process namespace sharing | +| deployment.terminationGracePeriodSeconds | int | `60` | Amount of time (in seconds) before Kubernetes will send the SIGKILL signal if Traefik does not shut down | +| env | list | See _values.yaml_ | Environment variables to be passed to Traefik's binary | +| envFrom | list | `[]` | Environment variables to be passed to Traefik's binary from configMaps or secrets | +| experimental.kubernetesGateway.enabled | bool | `false` | Enable traefik experimental GatewayClass CRD | +| experimental.plugins | object | `{}` | Enable traefik experimental plugins | +| extraObjects | list | `[]` | Extra objects to deploy (value evaluated as a template) In some cases, it can avoid the need for additional, extended or adhoc deployments. See #595 for more details and traefik/tests/values/extra.yaml for example. | +| gateway.annotations | string | `nil` | Additional gateway annotations (e.g. for cert-manager.io/issuer) | +| gateway.enabled | bool | `true` | When providers.kubernetesGateway.enabled, deploy a default gateway | +| gateway.listeners | object | `{"web":{"hostname":null,"namespacePolicy":null,"port":8000,"protocol":"HTTP"},"websecure":{"certificateRefs":null,"hostname":null,"mode":null,"namespacePolicy":null,"port":8443,"protocol":"HTTPS"}}` | Define listeners | +| gateway.listeners.web.hostname | string | `nil` | Optional hostname. See [Hostname](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.Hostname) | +| gateway.listeners.web.namespacePolicy | string | `nil` | Routes are restricted to namespace of the gateway [by default](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.FromNamespaces | +| gateway.listeners.web.port | int | `8000` | Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. The port must match a port declared in ports section. | +| gateway.listeners.websecure.certificateRefs | string | `nil` | Add certificates for TLS or HTTPS protocols. See [GatewayTLSConfig](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io%2fv1.GatewayTLSConfig) | +| gateway.listeners.websecure.hostname | string | `nil` | Optional hostname. See [Hostname](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.Hostname) | +| gateway.listeners.websecure.mode | string | `nil` | TLS behavior for the TLS session initiated by the client. See [TLSModeType](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.TLSModeType). | +| gateway.listeners.websecure.namespacePolicy | string | `nil` | Routes are restricted to namespace of the gateway [by default](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.FromNamespaces) | +| gateway.listeners.websecure.port | int | `8443` | Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. The port must match a port declared in ports section. | +| gateway.name | string | `nil` | Set a custom name to gateway | +| gateway.namespace | string | `nil` | By default, Gateway is created in the same `Namespace` than Traefik. | +| gatewayClass.enabled | bool | `true` | When providers.kubernetesGateway.enabled and gateway.enabled, deploy a default gatewayClass | +| gatewayClass.labels | string | `nil` | Additional gatewayClass labels (e.g. for filtering gateway objects by custom labels) | +| gatewayClass.name | string | `nil` | Set a custom name to GatewayClass | +| globalArguments | list | `["--global.checknewversion","--global.sendanonymoususage"]` | Global command arguments to be passed to all traefik's pods | +| hostNetwork | bool | `false` | If hostNetwork is true, runs traefik in the host network namespace To prevent unschedulabel pods due to port collisions, if hostNetwork=true and replicas>1, a pod anti-affinity is recommended and will be set if the affinity is left as default. | +| hub.apimanagement.admission.listenAddr | string | `nil` | WebHook admission server listen address. Default: "0.0.0.0:9943". | +| hub.apimanagement.admission.secretName | string | `nil` | Certificate of the WebHook admission server. Default: "hub-agent-cert". | +| hub.apimanagement.enabled | string | `nil` | Set to true in order to enable API Management. Requires a valid license token. | +| hub.ratelimit.redis.cluster | string | `nil` | Enable Redis Cluster. Default: true. | +| hub.ratelimit.redis.database | string | `nil` | Database used to store information. Default: "0". | +| hub.ratelimit.redis.endpoints | string | `nil` | Endpoints of the Redis instances to connect to. Default: "". | +| hub.ratelimit.redis.password | string | `nil` | The password to use when connecting to Redis endpoints. Default: "". | +| hub.ratelimit.redis.sentinel.masterset | string | `nil` | Name of the set of main nodes to use for main selection. Required when using Sentinel. Default: "". | +| hub.ratelimit.redis.sentinel.password | string | `nil` | Password to use for sentinel authentication (can be different from endpoint password). Default: "". | +| hub.ratelimit.redis.sentinel.username | string | `nil` | Username to use for sentinel authentication (can be different from endpoint username). Default: "". | +| hub.ratelimit.redis.timeout | string | `nil` | Timeout applied on connection with redis. Default: "0s". | +| hub.ratelimit.redis.tls.ca | string | `nil` | Path to the certificate authority used for the secured connection. | +| hub.ratelimit.redis.tls.cert | string | `nil` | Path to the public certificate used for the secure connection. | +| hub.ratelimit.redis.tls.insecureSkipVerify | string | `nil` | When insecureSkipVerify is set to true, the TLS connection accepts any certificate presented by the server. Default: false. | +| hub.ratelimit.redis.tls.key | string | `nil` | Path to the private key used for the secure connection. | +| hub.ratelimit.redis.username | string | `nil` | The username to use when connecting to Redis endpoints. Default: "". | +| hub.sendlogs | string | `nil` | | +| hub.token | string | `nil` | Name of `Secret` with key 'token' set to a valid license token. It enables API Gateway. | +| image.pullPolicy | string | `"IfNotPresent"` | Traefik image pull policy | +| image.registry | string | `"docker.io"` | Traefik image host registry | +| image.repository | string | `"traefik"` | Traefik image repository | +| image.tag | string | `nil` | defaults to appVersion | +| ingressClass | object | `{"enabled":true,"isDefaultClass":true}` | Create a default IngressClass for Traefik | +| ingressRoute.dashboard.annotations | object | `{}` | Additional ingressRoute annotations (e.g. for kubernetes.io/ingress.class) | +| ingressRoute.dashboard.enabled | bool | `false` | Create an IngressRoute for the dashboard | +| ingressRoute.dashboard.entryPoints | list | `["traefik"]` | Specify the allowed entrypoints to use for the dashboard ingress route, (e.g. traefik, web, websecure). By default, it's using traefik entrypoint, which is not exposed. /!\ Do not expose your dashboard without any protection over the internet /!\ | +| ingressRoute.dashboard.labels | object | `{}` | Additional ingressRoute labels (e.g. for filtering IngressRoute by custom labels) | +| ingressRoute.dashboard.matchRule | string | `"PathPrefix(`/dashboard`) || PathPrefix(`/api`)"` | The router match rule used for the dashboard ingressRoute | +| ingressRoute.dashboard.middlewares | list | `[]` | Additional ingressRoute middlewares (e.g. for authentication) | +| ingressRoute.dashboard.services | list | `[{"kind":"TraefikService","name":"api@internal"}]` | The internal service used for the dashboard ingressRoute | +| ingressRoute.dashboard.tls | object | `{}` | TLS options (e.g. secret containing certificate) | +| ingressRoute.healthcheck.annotations | object | `{}` | Additional ingressRoute annotations (e.g. for kubernetes.io/ingress.class) | +| ingressRoute.healthcheck.enabled | bool | `false` | Create an IngressRoute for the healthcheck probe | +| ingressRoute.healthcheck.entryPoints | list | `["traefik"]` | Specify the allowed entrypoints to use for the healthcheck ingress route, (e.g. traefik, web, websecure). By default, it's using traefik entrypoint, which is not exposed. | +| ingressRoute.healthcheck.labels | object | `{}` | Additional ingressRoute labels (e.g. for filtering IngressRoute by custom labels) | +| ingressRoute.healthcheck.matchRule | string | `"PathPrefix(`/ping`)"` | The router match rule used for the healthcheck ingressRoute | +| ingressRoute.healthcheck.middlewares | list | `[]` | Additional ingressRoute middlewares (e.g. for authentication) | +| ingressRoute.healthcheck.services | list | `[{"kind":"TraefikService","name":"ping@internal"}]` | The internal service used for the healthcheck ingressRoute | +| ingressRoute.healthcheck.tls | object | `{}` | TLS options (e.g. secret containing certificate) | +| instanceLabelOverride | string | `nil` | | +| livenessProbe.failureThreshold | int | `3` | The number of consecutive failures allowed before considering the probe as failed. | +| livenessProbe.initialDelaySeconds | int | `2` | The number of seconds to wait before starting the first probe. | +| livenessProbe.periodSeconds | int | `10` | The number of seconds to wait between consecutive probes. | +| livenessProbe.successThreshold | int | `1` | The minimum consecutive successes required to consider the probe successful. | +| livenessProbe.timeoutSeconds | int | `2` | The number of seconds to wait for a probe response before considering it as failed. | +| logs.access.addInternals | string | `nil` | Enables accessLogs for internal resources. Default: false. | +| logs.access.bufferingSize | string | `nil` | Set [bufferingSize](https://doc.traefik.io/traefik/observability/access-logs/#bufferingsize) | +| logs.access.enabled | bool | `false` | To enable access logs | +| logs.access.fields.general.defaultmode | string | `"keep"` | Available modes: keep, drop, redact. | +| logs.access.fields.general.names | object | `{}` | Names of the fields to limit. | +| logs.access.fields.headers | object | `{"defaultmode":"drop","names":{}}` | [Limit logged fields or headers](https://doc.traefik.io/traefik/observability/access-logs/#limiting-the-fieldsincluding-headers) | +| logs.access.fields.headers.defaultmode | string | `"drop"` | Available modes: keep, drop, redact. | +| logs.access.filters | object | `{}` | Set [filtering](https://docs.traefik.io/observability/access-logs/#filtering) | +| logs.access.format | string | `nil` | Set [access log format](https://doc.traefik.io/traefik/observability/access-logs/#format) | +| logs.general.format | string | `nil` | Set [logs format](https://doc.traefik.io/traefik/observability/logs/#format) @default common | +| logs.general.level | string | `"INFO"` | Alternative logging levels are DEBUG, PANIC, FATAL, ERROR, WARN, and INFO. | +| metrics.addInternals | string | `nil` | | +| metrics.otlp.addEntryPointsLabels | string | `nil` | Enable metrics on entry points. Default: true | +| metrics.otlp.addRoutersLabels | string | `nil` | Enable metrics on routers. Default: false | +| metrics.otlp.addServicesLabels | string | `nil` | Enable metrics on services. Default: true | +| metrics.otlp.enabled | bool | `false` | Set to true in order to enable the OpenTelemetry metrics | +| metrics.otlp.explicitBoundaries | string | `nil` | Explicit boundaries for Histogram data points. Default: [.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10] | +| metrics.otlp.grpc.enabled | bool | `false` | Set to true in order to send metrics to the OpenTelemetry Collector using gRPC | +| metrics.otlp.grpc.endpoint | string | `nil` | Format: ://:. Default: http://localhost:4318/v1/metrics | +| metrics.otlp.grpc.insecure | string | `nil` | Allows reporter to send metrics to the OpenTelemetry Collector without using a secured protocol. | +| metrics.otlp.grpc.tls.ca | string | `nil` | The path to the certificate authority, it defaults to the system bundle. | +| metrics.otlp.grpc.tls.cert | string | `nil` | The path to the public certificate. When using this option, setting the key option is required. | +| metrics.otlp.grpc.tls.insecureSkipVerify | string | `nil` | When set to true, the TLS connection accepts any certificate presented by the server regardless of the hostnames it covers. | +| metrics.otlp.grpc.tls.key | string | `nil` | The path to the private key. When using this option, setting the cert option is required. | +| metrics.otlp.http.enabled | bool | `false` | Set to true in order to send metrics to the OpenTelemetry Collector using HTTP. | +| metrics.otlp.http.endpoint | string | `nil` | Format: ://:. Default: http://localhost:4318/v1/metrics | +| metrics.otlp.http.headers | string | `nil` | Additional headers sent with metrics by the reporter to the OpenTelemetry Collector. | +| metrics.otlp.http.tls.ca | string | `nil` | The path to the certificate authority, it defaults to the system bundle. | +| metrics.otlp.http.tls.cert | string | `nil` | The path to the public certificate. When using this option, setting the key option is required. | +| metrics.otlp.http.tls.insecureSkipVerify | string | `nil` | When set to true, the TLS connection accepts any certificate presented by the server regardless of the hostnames it covers. | +| metrics.otlp.http.tls.key | string | `nil` | The path to the private key. When using this option, setting the cert option is required. | +| metrics.otlp.pushInterval | string | `nil` | Interval at which metrics are sent to the OpenTelemetry Collector. Default: 10s | +| metrics.prometheus.disableAPICheck | string | `nil` | When set to true, it won't check if Prometheus Operator CRDs are deployed | +| metrics.prometheus.entryPoint | string | `"metrics"` | Entry point used to expose metrics. | +| metrics.prometheus.prometheusRule.additionalLabels | string | `nil` | | +| metrics.prometheus.prometheusRule.enabled | bool | `false` | Enable optional CR for Prometheus Operator. See EXAMPLES.md for more details. | +| metrics.prometheus.prometheusRule.namespace | string | `nil` | | +| metrics.prometheus.service.annotations | string | `nil` | | +| metrics.prometheus.service.enabled | string | `nil` | Create a dedicated metrics service to use with ServiceMonitor | +| metrics.prometheus.service.labels | string | `nil` | | +| metrics.prometheus.serviceMonitor.additionalLabels | string | `nil` | | +| metrics.prometheus.serviceMonitor.enableHttp2 | string | `nil` | | +| metrics.prometheus.serviceMonitor.enabled | bool | `false` | Enable optional CR for Prometheus Operator. See EXAMPLES.md for more details. | +| metrics.prometheus.serviceMonitor.followRedirects | string | `nil` | | +| metrics.prometheus.serviceMonitor.honorLabels | string | `nil` | | +| metrics.prometheus.serviceMonitor.honorTimestamps | string | `nil` | | +| metrics.prometheus.serviceMonitor.interval | string | `nil` | | +| metrics.prometheus.serviceMonitor.jobLabel | string | `nil` | | +| metrics.prometheus.serviceMonitor.metricRelabelings | string | `nil` | | +| metrics.prometheus.serviceMonitor.namespace | string | `nil` | | +| metrics.prometheus.serviceMonitor.namespaceSelector | string | `nil` | | +| metrics.prometheus.serviceMonitor.relabelings | string | `nil` | | +| metrics.prometheus.serviceMonitor.scrapeTimeout | string | `nil` | | +| namespaceOverride | string | `nil` | This field override the default Release Namespace for Helm. It will not affect optional CRDs such as `ServiceMonitor` and `PrometheusRules` | +| nodeSelector | object | `{}` | nodeSelector is the simplest recommended form of node selection constraint. | +| persistence.accessMode | string | `"ReadWriteOnce"` | | +| persistence.annotations | object | `{}` | | +| persistence.enabled | bool | `false` | Enable persistence using Persistent Volume Claims ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ It can be used to store TLS certificates, see `storage` in certResolvers | +| persistence.name | string | `"data"` | | +| persistence.path | string | `"/data"` | | +| persistence.size | string | `"128Mi"` | | +| podDisruptionBudget | object | `{"enabled":null,"maxUnavailable":null,"minAvailable":null}` | [Pod Disruption Budget](https://kubernetes.io/docs/reference/kubernetes-api/policy-resources/pod-disruption-budget-v1/) | +| podSecurityContext | object | See _values.yaml_ | [Pod Security Context](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#security-context) | +| podSecurityPolicy | object | `{"enabled":false}` | Enable to create a PodSecurityPolicy and assign it to the Service Account via RoleBinding or ClusterRoleBinding | +| ports.metrics.expose | object | `{"default":false}` | You may not want to expose the metrics port on production deployments. If you want to access it from outside your cluster, use `kubectl port-forward` or create a secure ingress | +| ports.metrics.exposedPort | int | `9100` | The exposed port for this service | +| ports.metrics.port | int | `9100` | When using hostNetwork, use another port to avoid conflict with node exporter: https://github.com/prometheus/prometheus/wiki/Default-port-allocations | +| ports.metrics.protocol | string | `"TCP"` | The port protocol (TCP/UDP) | +| ports.traefik.expose | object | `{"default":false}` | You SHOULD NOT expose the traefik port on production deployments. If you want to access it from outside your cluster, use `kubectl port-forward` or create a secure ingress | +| ports.traefik.exposedPort | int | `9000` | The exposed port for this service | +| ports.traefik.port | int | `9000` | | +| ports.traefik.protocol | string | `"TCP"` | The port protocol (TCP/UDP) | +| ports.web.expose.default | bool | `true` | | +| ports.web.exposedPort | int | `80` | | +| ports.web.port | int | `8000` | | +| ports.web.protocol | string | `"TCP"` | | +| ports.web.transport | object | `{"keepAliveMaxRequests":null,"keepAliveMaxTime":null,"lifeCycle":{"graceTimeOut":null,"requestAcceptGraceTimeout":null},"respondingTimeouts":{"idleTimeout":null,"readTimeout":null,"writeTimeout":null}}` | Set transport settings for the entrypoint; see also https://doc.traefik.io/traefik/routing/entrypoints/#transport | +| ports.websecure.expose.default | bool | `true` | | +| ports.websecure.exposedPort | int | `443` | | +| ports.websecure.http3.enabled | bool | `false` | | +| ports.websecure.middlewares | list | `[]` | /!\ It introduces here a link between your static configuration and your dynamic configuration /!\ It follows the provider naming convention: https://doc.traefik.io/traefik/providers/overview/#provider-namespace middlewares: - namespace-name1@kubernetescrd - namespace-name2@kubernetescrd | +| ports.websecure.port | int | `8443` | | +| ports.websecure.protocol | string | `"TCP"` | | +| ports.websecure.tls.certResolver | string | `""` | | +| ports.websecure.tls.domains | list | `[]` | | +| ports.websecure.tls.enabled | bool | `true` | | +| ports.websecure.tls.options | string | `""` | | +| ports.websecure.transport | object | `{"keepAliveMaxRequests":null,"keepAliveMaxTime":null,"lifeCycle":{"graceTimeOut":null,"requestAcceptGraceTimeout":null},"respondingTimeouts":{"idleTimeout":null,"readTimeout":null,"writeTimeout":null}}` | Set transport settings for the entrypoint; see also https://doc.traefik.io/traefik/routing/entrypoints/#transport | +| priorityClassName | string | `""` | [Pod Priority and Preemption](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/) | +| providers.file.content | string | `nil` | File content (YAML format, go template supported) (see https://doc.traefik.io/traefik/providers/file/) | +| providers.file.enabled | bool | `false` | Create a file provider | +| providers.file.watch | bool | `true` | Allows Traefik to automatically watch for file changes | +| providers.kubernetesCRD.allowCrossNamespace | bool | `false` | Allows IngressRoute to reference resources in namespace other than theirs | +| providers.kubernetesCRD.allowEmptyServices | bool | `false` | Allows to return 503 when there is no endpoints available | +| providers.kubernetesCRD.allowExternalNameServices | bool | `false` | Allows to reference ExternalName services in IngressRoute | +| providers.kubernetesCRD.enabled | bool | `true` | Load Kubernetes IngressRoute provider | +| providers.kubernetesCRD.ingressClass | string | `nil` | When the parameter is set, only resources containing an annotation with the same value are processed. Otherwise, resources missing the annotation, having an empty value, or the value traefik are processed. It will also set required annotation on Dashboard and Healthcheck IngressRoute when enabled. | +| providers.kubernetesCRD.namespaces | list | `[]` | Array of namespaces to watch. If left empty, Traefik watches all namespaces. | +| providers.kubernetesCRD.nativeLBByDefault | string | `nil` | Defines whether to use Native Kubernetes load-balancing mode by default. | +| providers.kubernetesGateway.enabled | bool | `false` | Enable Traefik Gateway provider for Gateway API | +| providers.kubernetesGateway.experimentalChannel | bool | `false` | Toggles support for the Experimental Channel resources (Gateway API release channels documentation). This option currently enables support for TCPRoute and TLSRoute. | +| providers.kubernetesGateway.labelselector | string | `nil` | A label selector can be defined to filter on specific GatewayClass objects only. | +| providers.kubernetesGateway.namespaces | list | `[]` | Array of namespaces to watch. If left empty, Traefik watches all namespaces. | +| providers.kubernetesIngress.allowEmptyServices | bool | `false` | Allows to return 503 when there is no endpoints available | +| providers.kubernetesIngress.allowExternalNameServices | bool | `false` | Allows to reference ExternalName services in Ingress | +| providers.kubernetesIngress.disableIngressClassLookup | bool | `false` | | +| providers.kubernetesIngress.enabled | bool | `true` | Load Kubernetes Ingress provider | +| providers.kubernetesIngress.ingressClass | string | `nil` | When ingressClass is set, only Ingresses containing an annotation with the same value are processed. Otherwise, Ingresses missing the annotation, having an empty value, or the value traefik are processed. | +| providers.kubernetesIngress.namespaces | list | `[]` | Array of namespaces to watch. If left empty, Traefik watches all namespaces. | +| providers.kubernetesIngress.nativeLBByDefault | string | `nil` | Defines whether to use Native Kubernetes load-balancing mode by default. | +| providers.kubernetesIngress.publishedService.enabled | bool | `false` | | +| rbac | object | `{"enabled":true,"namespaced":false,"secretResourceNames":[]}` | Whether Role Based Access Control objects like roles and rolebindings should be created | +| readinessProbe.failureThreshold | int | `1` | The number of consecutive failures allowed before considering the probe as failed. | +| readinessProbe.initialDelaySeconds | int | `2` | The number of seconds to wait before starting the first probe. | +| readinessProbe.periodSeconds | int | `10` | The number of seconds to wait between consecutive probes. | +| readinessProbe.successThreshold | int | `1` | The minimum consecutive successes required to consider the probe successful. | +| readinessProbe.timeoutSeconds | int | `2` | The number of seconds to wait for a probe response before considering it as failed. | +| resources | object | `{}` | [Resources](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for `traefik` container. | +| securityContext | object | See _values.yaml_ | [SecurityContext](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#security-context-1) | +| service.additionalServices | object | `{}` | | +| service.annotations | object | `{}` | Additional annotations applied to both TCP and UDP services (e.g. for cloud provider specific config) | +| service.annotationsTCP | object | `{}` | Additional annotations for TCP service only | +| service.annotationsUDP | object | `{}` | Additional annotations for UDP service only | +| service.enabled | bool | `true` | | +| service.externalIPs | list | `[]` | | +| service.labels | object | `{}` | Additional service labels (e.g. for filtering Service by custom labels) | +| service.loadBalancerSourceRanges | list | `[]` | | +| service.single | bool | `true` | | +| service.spec | object | `{}` | Cannot contain type, selector or ports entries. | +| service.type | string | `"LoadBalancer"` | | +| serviceAccount | object | `{"name":""}` | The service account the pods will use to interact with the Kubernetes API | +| serviceAccountAnnotations | object | `{}` | Additional serviceAccount annotations (e.g. for oidc authentication) | +| startupProbe | string | `nil` | Define [Startup Probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-startup-probes) | +| tlsOptions | object | `{}` | TLS Options are created as [TLSOption CRDs](https://doc.traefik.io/traefik/https/tls/#tls-options) When using `labelSelector`, you'll need to set labels on tlsOption accordingly. See EXAMPLE.md for details. | +| tlsStore | object | `{}` | TLS Store are created as [TLSStore CRDs](https://doc.traefik.io/traefik/https/tls/#default-certificate). This is useful if you want to set a default certificate. See EXAMPLE.md for details. | +| tolerations | list | `[]` | Tolerations allow the scheduler to schedule pods with matching taints. | +| topologySpreadConstraints | list | `[]` | You can use topology spread constraints to control how Pods are spread across your cluster among failure-domains. | +| tracing | object | `{"addInternals":null,"otlp":{"enabled":false,"grpc":{"enabled":false,"endpoint":null,"insecure":null,"tls":{"ca":null,"cert":null,"insecureSkipVerify":null,"key":null}},"http":{"enabled":false,"endpoint":null,"headers":null,"tls":{"ca":null,"cert":null,"insecureSkipVerify":null,"key":null}}}}` | https://doc.traefik.io/traefik/observability/tracing/overview/ | +| tracing.addInternals | string | `nil` | Enables tracing for internal resources. Default: false. | +| tracing.otlp.enabled | bool | `false` | See https://doc.traefik.io/traefik/v3.0/observability/tracing/opentelemetry/ | +| tracing.otlp.grpc.enabled | bool | `false` | Set to true in order to send metrics to the OpenTelemetry Collector using gRPC | +| tracing.otlp.grpc.endpoint | string | `nil` | Format: ://:. Default: http://localhost:4318/v1/metrics | +| tracing.otlp.grpc.insecure | string | `nil` | Allows reporter to send metrics to the OpenTelemetry Collector without using a secured protocol. | +| tracing.otlp.grpc.tls.ca | string | `nil` | The path to the certificate authority, it defaults to the system bundle. | +| tracing.otlp.grpc.tls.cert | string | `nil` | The path to the public certificate. When using this option, setting the key option is required. | +| tracing.otlp.grpc.tls.insecureSkipVerify | string | `nil` | When set to true, the TLS connection accepts any certificate presented by the server regardless of the hostnames it covers. | +| tracing.otlp.grpc.tls.key | string | `nil` | The path to the private key. When using this option, setting the cert option is required. | +| tracing.otlp.http.enabled | bool | `false` | Set to true in order to send metrics to the OpenTelemetry Collector using HTTP. | +| tracing.otlp.http.endpoint | string | `nil` | Format: ://:. Default: http://localhost:4318/v1/metrics | +| tracing.otlp.http.headers | string | `nil` | Additional headers sent with metrics by the reporter to the OpenTelemetry Collector. | +| tracing.otlp.http.tls.ca | string | `nil` | The path to the certificate authority, it defaults to the system bundle. | +| tracing.otlp.http.tls.cert | string | `nil` | The path to the public certificate. When using this option, setting the key option is required. | +| tracing.otlp.http.tls.insecureSkipVerify | string | `nil` | When set to true, the TLS connection accepts any certificate presented by the server regardless of the hostnames it covers. | +| tracing.otlp.http.tls.key | string | `nil` | The path to the private key. When using this option, setting the cert option is required. | +| updateStrategy.rollingUpdate.maxSurge | int | `1` | | +| updateStrategy.rollingUpdate.maxUnavailable | int | `0` | | +| updateStrategy.type | string | `"RollingUpdate"` | Customize updateStrategy: RollingUpdate or OnDelete | +| volumes | list | `[]` | Add volumes to the traefik pod. The volume name will be passed to tpl. This can be used to mount a cert pair or a configmap that holds a config.toml file. After the volume has been mounted, add the configs into traefik by using the `additionalArguments` list below, eg: `additionalArguments: - "--providers.file.filename=/config/dynamic.toml" - "--ping" - "--ping.entrypoint=web"` | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) diff --git a/charts/traefik/traefik/30.0.2/app-readme.md b/charts/traefik/traefik/30.0.2/app-readme.md new file mode 100644 index 000000000..7289af5d0 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/app-readme.md @@ -0,0 +1,5 @@ +# Traefik Proxy + +[Traefik](https://traefik.io/) is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease. + +This chart bootstraps Traefik version 2 as a Kubernetes ingress controller. diff --git a/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_backendtlspolicies.yaml b/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_backendtlspolicies.yaml new file mode 100644 index 000000000..bb90c54d0 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_backendtlspolicies.yaml @@ -0,0 +1,281 @@ +# Copyright 2023 The Kubernetes Authors. +# +# 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. + +# +# Gateway API Experimental channel install +# +# +# config/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + labels: + gateway.networking.k8s.io/policy: Direct + name: backendtlspolicies.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: BackendTLSPolicy + listKind: BackendTLSPolicyList + plural: backendtlspolicies + shortNames: + - btlspolicy + singular: backendtlspolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: BackendTLSPolicy provides a way to configure how a Gateway connects to a Backend via TLS. + 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: Spec defines the desired state of BackendTLSPolicy. + properties: + targetRef: + description: "TargetRef identifies an API object to apply the policy to. Only Services have Extended support. Implementations MAY support additional objects, with Implementation Specific support. Note that this config applies to the entire referenced resource by default, but this default may change in the future to provide a more granular application of the policy. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. When unspecified, the local namespace is inferred. Even when policy targets a resource in a different namespace, it MUST only apply to traffic originating from the same namespace as the policy. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + sectionName: + description: "SectionName is the name of a section within the target resource. When unspecified, this targetRef targets the entire resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name * Service: Port Name \n If a SectionName is specified, but does not exist on the targeted object, the Policy must fail to attach, and the policy implementation should record a `ResolvedRefs` or similar Condition in the Policy's status." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + tls: + description: TLS contains backend TLS policy configuration. + properties: + caCertRefs: + description: "CACertRefs contains one or more references to Kubernetes objects that contain a PEM-encoded TLS CA certificate bundle, which is used to validate a TLS handshake between the Gateway and backend Pod. \n If CACertRefs is empty or unspecified, then WellKnownCACerts must be specified. Only one of CACertRefs or WellKnownCACerts may be specified, not both. If CACertRefs is empty or unspecified, the configuration for WellKnownCACerts MUST be honored instead. \n References to a resource in a different namespace are invalid for the moment, although we will revisit this in the future. \n A single CACertRef to a Kubernetes ConfigMap kind has \"Core\" support. Implementations MAY choose to support attaching multiple certificates to a backend, but this behavior is implementation-specific. \n Support: Core - An optional single reference to a Kubernetes ConfigMap, with the CA certificate in a key named `ca.crt`. \n Support: Implementation-specific (More than one reference, or other kinds of resources)." + items: + description: "LocalObjectReference identifies an API object within the namespace of the referrer. The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + type: array + hostname: + description: "Hostname is used for two purposes in the connection between Gateways and backends: \n 1. Hostname MUST be used as the SNI to connect to the backend (RFC 6066). 2. Hostname MUST be used for authentication and MUST match the certificate served by the matching backend. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + wellKnownCACerts: + description: "WellKnownCACerts specifies whether system CA certificates may be used in the TLS handshake between the gateway and backend pod. \n If WellKnownCACerts is unspecified or empty (\"\"), then CACertRefs must be specified with at least one entry for a valid configuration. Only one of CACertRefs or WellKnownCACerts may be specified, not both. \n Support: Core for \"System\"" + enum: + - System + type: string + required: + - hostname + type: object + x-kubernetes-validations: + - message: must not contain both CACertRefs and WellKnownCACerts + rule: '!(has(self.caCertRefs) && size(self.caCertRefs) > 0 && has(self.wellKnownCACerts) && self.wellKnownCACerts != "")' + - message: must specify either CACertRefs or WellKnownCACerts + rule: (has(self.caCertRefs) && size(self.caCertRefs) > 0 || has(self.wellKnownCACerts) && self.wellKnownCACerts != "") + required: + - targetRef + - tls + type: object + status: + description: Status defines the current state of BackendTLSPolicy. + properties: + ancestors: + description: "Ancestors is a list of ancestor resources (usually Gateways) that are associated with the policy, and the status of the policy with respect to each ancestor. When this policy attaches to a parent, the controller that manages the parent and the ancestors MUST add an entry to this list when the controller first sees the policy and SHOULD update the entry as appropriate when the relevant ancestor is modified. \n Note that choosing the relevant ancestor is left to the Policy designers; an important part of Policy design is designing the right object level at which to namespace this status. \n Note also that implementations MUST ONLY populate ancestor status for the Ancestor resources they are responsible for. Implementations MUST use the ControllerName field to uniquely identify the entries in this list that they are responsible for. \n Note that to achieve this, the list of PolicyAncestorStatus structs MUST be treated as a map with a composite key, made up of the AncestorRef and ControllerName fields combined. \n A maximum of 16 ancestors will be represented in this list. An empty list means the Policy is not relevant for any ancestors. \n If this slice is full, implementations MUST NOT add further entries. Instead they MUST consider the policy unimplementable and signal that on any related resources such as the ancestor that would be referenced here. For example, if this list was full on BackendTLSPolicy, no additional Gateways would be able to reference the Service targeted by the BackendTLSPolicy." + items: + description: "PolicyAncestorStatus describes the status of a route with respect to an associated Ancestor. \n Ancestors refer to objects that are either the Target of a policy or above it in terms of object hierarchy. For example, if a policy targets a Service, the Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most useful object to place Policy status on, so we recommend that implementations SHOULD use Gateway as the PolicyAncestorStatus object unless the designers have a _very_ good reason otherwise. \n In the context of policy attachment, the Ancestor is used to distinguish which resource results in a distinct application of this policy. For example, if a policy targets a Service, it may have a distinct result per attached Gateway. \n Policies targeting the same resource may have different effects depending on the ancestors of those resources. For example, different Gateways targeting the same Service may have different capabilities, especially if they have different underlying implementations. \n For example, in BackendTLSPolicy, the Policy attaches to a Service that is used as a backend in a HTTPRoute that is itself attached to a Gateway. In this case, the relevant object for status is the Gateway, and that is the ancestor object referred to in this status. \n Note that a parent is also an ancestor, so for objects where the parent is the relevant object for status, this struct SHOULD still be used. \n This struct is intended to be used in a slice that's effectively a map, with a composite key made up of the AncestorRef and the ControllerName." + properties: + ancestorRef: + description: AncestorRef corresponds with a ParentRef in the spec that this PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with respect to the given Ancestor. + 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 }" + 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 + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_gatewayclasses.yaml b/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_gatewayclasses.yaml new file mode 100644 index 000000000..7ebf7c706 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_gatewayclasses.yaml @@ -0,0 +1,381 @@ +# +# config/crd/experimental/gateway.networking.k8s.io_gatewayclasses.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: gatewayclasses.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GatewayClass + listKind: GatewayClassList + plural: gatewayclasses + shortNames: + - gc + singular: gatewayclass + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1 + schema: + openAPIV3Schema: + description: "GatewayClass describes a class of Gateways available to the user for creating Gateway resources. \n It is recommended that this resource be used as a template for Gateways. This means that a Gateway is based on the state of the GatewayClass at the time it was created and changes to the GatewayClass or associated parameters are not propagated down to existing Gateways. This recommendation is intended to limit the blast radius of changes to GatewayClass or associated parameters. If implementations choose to propagate GatewayClass changes to existing Gateways, that MUST be clearly documented by the implementation. \n Whenever one or more Gateways are using a GatewayClass, implementations SHOULD add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the associated GatewayClass. This ensures that a GatewayClass associated with a Gateway is not deleted while in use. \n GatewayClass is a Cluster level resource." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: "ControllerName is the name of the controller that is managing Gateways of this class. The value of this field MUST be a domain prefixed path. \n Example: \"example.net/gateway-controller\". \n This field is not mutable and cannot be empty. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: "ParametersRef is a reference to a resource that contains the configuration parameters corresponding to the GatewayClass. This is optional if the controller does not require any additional configuration. \n ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, or an implementation-specific custom resource. The resource can be cluster-scoped or namespace-scoped. \n If the referent cannot be found, the GatewayClass's \"InvalidParameters\" status condition will be true. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. This field is required when referring to a Namespace-scoped resource and MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: Accepted + description: "Status defines the current state of GatewayClass. \n Implementations MUST populate status on all GatewayClass resources which specify their controller name." + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: "Conditions is the current status from the controller for this GatewayClass. \n Controllers should prefer to publish conditions using values of GatewayClassConditionType for the type of each Condition." + 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 }" + 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 + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + supportedFeatures: + description: 'SupportedFeatures is the set of features the GatewayClass support. It MUST be sorted in ascending alphabetical order. ' + items: + description: SupportedFeature is used to describe distinct features that are covered by conformance tests. + enum: + - Gateway + - GatewayPort8080 + - GatewayStaticAddresses + - HTTPRoute + - HTTPRouteDestinationPortMatching + - HTTPRouteHostRewrite + - HTTPRouteMethodMatching + - HTTPRoutePathRedirect + - HTTPRoutePathRewrite + - HTTPRoutePortRedirect + - HTTPRouteQueryParamMatching + - HTTPRouteRequestMirror + - HTTPRouteRequestMultipleMirrors + - HTTPRouteResponseHeaderModification + - HTTPRouteSchemeRedirect + - Mesh + - ReferenceGrant + - TLSRoute + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: "GatewayClass describes a class of Gateways available to the user for creating Gateway resources. \n It is recommended that this resource be used as a template for Gateways. This means that a Gateway is based on the state of the GatewayClass at the time it was created and changes to the GatewayClass or associated parameters are not propagated down to existing Gateways. This recommendation is intended to limit the blast radius of changes to GatewayClass or associated parameters. If implementations choose to propagate GatewayClass changes to existing Gateways, that MUST be clearly documented by the implementation. \n Whenever one or more Gateways are using a GatewayClass, implementations SHOULD add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the associated GatewayClass. This ensures that a GatewayClass associated with a Gateway is not deleted while in use. \n GatewayClass is a Cluster level resource." + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: "ControllerName is the name of the controller that is managing Gateways of this class. The value of this field MUST be a domain prefixed path. \n Example: \"example.net/gateway-controller\". \n This field is not mutable and cannot be empty. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: "ParametersRef is a reference to a resource that contains the configuration parameters corresponding to the GatewayClass. This is optional if the controller does not require any additional configuration. \n ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, or an implementation-specific custom resource. The resource can be cluster-scoped or namespace-scoped. \n If the referent cannot be found, the GatewayClass's \"InvalidParameters\" status condition will be true. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace is the namespace of the referent. This field is required when referring to a Namespace-scoped resource and MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Waiting + status: Unknown + type: Accepted + description: "Status defines the current state of GatewayClass. \n Implementations MUST populate status on all GatewayClass resources which specify their controller name." + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: "Conditions is the current status from the controller for this GatewayClass. \n Controllers should prefer to publish conditions using values of GatewayClassConditionType for the type of each Condition." + 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 }" + 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 + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + supportedFeatures: + description: 'SupportedFeatures is the set of features the GatewayClass support. It MUST be sorted in ascending alphabetical order. ' + items: + description: SupportedFeature is used to describe distinct features that are covered by conformance tests. + enum: + - Gateway + - GatewayPort8080 + - GatewayStaticAddresses + - HTTPRoute + - HTTPRouteDestinationPortMatching + - HTTPRouteHostRewrite + - HTTPRouteMethodMatching + - HTTPRoutePathRedirect + - HTTPRoutePathRewrite + - HTTPRoutePortRedirect + - HTTPRouteQueryParamMatching + - HTTPRouteRequestMirror + - HTTPRouteRequestMultipleMirrors + - HTTPRouteResponseHeaderModification + - HTTPRouteSchemeRedirect + - Mesh + - ReferenceGrant + - TLSRoute + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_gateways.yaml b/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_gateways.yaml new file mode 100644 index 000000000..2a4b26f58 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_gateways.yaml @@ -0,0 +1,1037 @@ +# +# config/crd/experimental/gateway.networking.k8s.io_gateways.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: gateways.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: Gateway + listKind: GatewayList + plural: gateways + shortNames: + - gtw + singular: gateway + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: Gateway represents an instance of a service-traffic handling infrastructure by binding Listeners to a set of IP addresses. + 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: Spec defines the desired state of Gateway. + properties: + addresses: + description: "Addresses requested for this Gateway. This is optional and behavior can depend on the implementation. If a value is set in the spec and the requested address is invalid or unavailable, the implementation MUST indicate this in the associated entry in GatewayStatus.Addresses. \n The Addresses field represents a request for the address(es) on the \"outside of the Gateway\", that traffic bound for this Gateway will use. This could be the IP address or hostname of an external load balancer or other networking infrastructure, or some other address that traffic will be sent to. \n If no Addresses are specified, the implementation MAY schedule the Gateway in an implementation-specific manner, assigning an appropriate set of Addresses. \n The implementation MUST bind all Listeners to every GatewayAddress that it assigns to the Gateway and add a corresponding entry in GatewayStatus.Addresses. \n Support: Extended \n " + items: + description: GatewayAddress describes an address that can be bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: IPAddress values must be unique + rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, a2.type == a1.type && a2.value == a1.value) : true )' + - message: Hostname values must be unique + rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, a2.type == a1.type && a2.value == a1.value) : true )' + gatewayClassName: + description: GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + infrastructure: + description: "Infrastructure defines infrastructure level attributes about this Gateway instance. \n Support: Core \n " + properties: + annotations: + additionalProperties: + description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Annotations that SHOULD be applied to any resources created in response to this Gateway. \n For implementations creating other Kubernetes objects, this should be the `metadata.annotations` field on resources. For other implementations, this refers to any relevant (implementation specific) \"annotations\" concepts. \n An implementation may chose to add additional implementation-specific annotations as they see fit. \n Support: Extended" + maxProperties: 8 + type: object + labels: + additionalProperties: + description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Labels that SHOULD be applied to any resources created in response to this Gateway. \n For implementations creating other Kubernetes objects, this should be the `metadata.labels` field on resources. For other implementations, this refers to any relevant (implementation specific) \"labels\" concepts. \n An implementation may chose to add additional implementation-specific labels as they see fit. \n Support: Extended" + maxProperties: 8 + type: object + type: object + listeners: + description: "Listeners associated with this Gateway. Listeners define logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. \n Each Listener in a set of Listeners (for example, in a single Gateway) MUST be _distinct_, in that a traffic flow MUST be able to be assigned to exactly one listener. (This section uses \"set of Listeners\" rather than \"Listeners in a single Gateway\" because implementations MAY merge configuration from multiple Gateways onto a single data plane, and these rules _also_ apply in that case). \n Practically, this means that each listener in a set MUST have a unique combination of Port, Protocol, and, if supported by the protocol, Hostname. \n Some combinations of port, protocol, and TLS settings are considered Core support and MUST be supported by implementations based on their targeted conformance profile: \n HTTP Profile \n 1. HTTPRoute, Port: 80, Protocol: HTTP 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided \n TLS Profile \n 1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough \n \"Distinct\" Listeners have the following property: \n The implementation can match inbound requests to a single distinct Listener. When multiple Listeners share values for fields (for example, two Listeners with the same Port value), the implementation can match requests to only one of the Listeners using other Listener fields. \n For example, the following Listener scenarios are distinct: \n 1. Multiple Listeners with the same Port that all use the \"HTTP\" Protocol that all have unique Hostname values. 2. Multiple Listeners with the same Port that use either the \"HTTPS\" or \"TLS\" Protocol that all have unique Hostname values. 3. A mixture of \"TCP\" and \"UDP\" Protocol Listeners, where no Listener with the same Protocol has the same Port value. \n Some fields in the Listener struct have possible values that affect whether the Listener is distinct. Hostname is particularly relevant for HTTP or HTTPS protocols. \n When using the Hostname value to select between same-Port, same-Protocol Listeners, the Hostname value must be different on each Listener for the Listener to be distinct. \n When the Listeners are distinct based on Hostname, inbound request hostnames MUST match from the most specific to least specific Hostname values to choose the correct Listener and its associated set of Routes. \n Exact matches must be processed before wildcard matches, and wildcard matches must be processed before fallback (empty Hostname value) matches. For example, `\"foo.example.com\"` takes precedence over `\"*.example.com\"`, and `\"*.example.com\"` takes precedence over `\"\"`. \n Additionally, if there are multiple wildcard entries, more specific wildcard entries must be processed before less specific wildcard entries. For example, `\"*.foo.example.com\"` takes precedence over `\"*.example.com\"`. The precise definition here is that the higher the number of dots in the hostname to the right of the wildcard character, the higher the precedence. \n The wildcard character will match any number of characters _and dots_ to the left, however, so `\"*.example.com\"` will match both `\"foo.bar.example.com\"` _and_ `\"bar.example.com\"`. \n If a set of Listeners contains Listeners that are not distinct, then those Listeners are Conflicted, and the implementation MUST set the \"Conflicted\" condition in the Listener Status to \"True\". \n Implementations MAY choose to accept a Gateway with some Conflicted Listeners only if they only accept the partial Listener set that contains no Conflicted Listeners. To put this another way, implementations may accept a partial Listener set only if they throw out *all* the conflicting Listeners. No picking one of the conflicting listeners as the winner. This also means that the Gateway must have at least one non-conflicting Listener in this case, otherwise it violates the requirement that at least one Listener must be present. \n The implementation MUST set a \"ListenersNotValid\" condition on the Gateway Status when the Gateway contains Conflicted Listeners whether or not they accept the Gateway. That Condition SHOULD clearly indicate in the Message which Listeners are conflicted, and which are Accepted. Additionally, the Listener status for those listeners SHOULD indicate which Listeners are conflicted and not Accepted. \n A Gateway's Listeners are considered \"compatible\" if: \n 1. They are distinct. 2. The implementation can serve them in compliance with the Addresses requirement that all Listeners are available on all assigned addresses. \n Compatible combinations in Extended support are expected to vary across implementations. A combination that is compatible for one implementation may not be compatible for another. \n For example, an implementation that cannot serve both TCP and UDP listeners on the same address, or cannot mix HTTPS and generic TLS listens on the same port would not consider those cases compatible, even though they are distinct. \n Note that requests SHOULD match at most one Listener. For example, if Listeners are defined for \"foo.example.com\" and \"*.example.com\", a request to \"foo.example.com\" SHOULD only be routed using routes attached to the \"foo.example.com\" Listener (and not the \"*.example.com\" Listener). This concept is known as \"Listener Isolation\". Implementations that do not support Listener Isolation MUST clearly document this. \n Implementations MAY merge separate Gateways onto a single set of Addresses if all Listeners across all Gateways are compatible. \n Support: Core" + items: + description: Listener embodies the concept of a logical endpoint where a Gateway accepts network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: "AllowedRoutes defines the types of routes that MAY be attached to a Listener and the trusted namespaces where those Route resources MAY be present. \n Although a client request may match multiple route rules, only one rule may ultimately receive the request. Matching precedence MUST be determined in order of the following criteria: \n * The most specific match as defined by the Route type. * The oldest Route based on creation timestamp. For example, a Route with a creation timestamp of \"2020-09-08 01:02:03\" is given precedence over a Route with a creation timestamp of \"2020-09-08 01:02:04\". * If everything else is equivalent, the Route appearing first in alphabetical order (namespace/name) should be given precedence. For example, foo/bar is given precedence over foo/baz. \n All valid rules within a Route attached to this Listener should be implemented. Invalid Route rules can be ignored (sometimes that will mean the full Route). If a Route rule transitions from valid to invalid, support for that Route rule should be dropped to ensure consistency. For example, even if a filter specified by a Route rule is invalid, the rest of the rules within that Route should still be supported. \n Support: Core" + properties: + kinds: + description: "Kinds specifies the groups and kinds of Routes that are allowed to bind to this Gateway Listener. When unspecified or empty, the kinds of Routes selected are determined using the Listener protocol. \n A RouteGroupKind MUST correspond to kinds of Routes that are compatible with the application protocol specified in the Listener's Protocol field. If an implementation does not support or recognize this resource type, it MUST set the \"ResolvedRefs\" condition to False for this Listener with the \"InvalidRouteKinds\" reason. \n Support: Core" + items: + description: RouteGroupKind indicates the group and kind of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: "Namespaces indicates namespaces from which Routes may be attached to this Listener. This is restricted to the namespace of this Gateway by default. \n Support: Core" + properties: + from: + default: Same + description: "From indicates where Routes will be selected for this Gateway. Possible values are: \n * All: Routes in all namespaces may be used by this Gateway. * Selector: Routes in namespaces selected by the selector may be used by this Gateway. * Same: Only Routes in the same namespace may be used by this Gateway. \n Support: Core" + enum: + - All + - Selector + - Same + type: string + selector: + description: "Selector must be specified when From is set to \"Selector\". In that case, only Routes in Namespaces matching this Selector will be selected by this Gateway. This field is ignored for other values of \"From\". \n Support: Core" + 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 + type: object + type: object + hostname: + description: "Hostname specifies the virtual hostname to match for protocol types that define this concept. When unspecified, all hostnames are matched. This field is ignored for protocols that don't require hostname based matching. \n Implementations MUST apply Hostname matching appropriately for each of the following protocols: \n * TLS: The Listener Hostname MUST match the SNI. * HTTP: The Listener Hostname MUST match the Host header of the request. * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP protocol layers as described above. If an implementation does not ensure that both the SNI and Host header match the Listener hostname, it MUST clearly document that. \n For HTTPRoute and TLSRoute resources, there is an interaction with the `spec.hostnames` array. When both listener and route specify hostnames, there MUST be an intersection between the values for a Route to be accepted. For more information, refer to the Route specific Hostnames documentation. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: "Name is the name of the Listener. This name MUST be unique within a Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: "Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: "Protocol specifies the network protocol this listener expects to receive. \n Support: Core" + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: "TLS is the TLS configuration for the Listener. This field is required if the Protocol field is \"HTTPS\" or \"TLS\". It is invalid to set this field if the Protocol field is \"HTTP\", \"TCP\", or \"UDP\". \n The association of SNIs to Certificate defined in GatewayTLSConfig is defined based on the Hostname field for this listener. \n The GatewayClass MUST use the longest matching SNI out of all available certificates for any TLS handshake. \n Support: Core" + properties: + certificateRefs: + description: "CertificateRefs contains a series of references to Kubernetes objects that contains TLS certificates and private keys. These certificates are used to establish a TLS handshake for requests that match the hostname of the associated listener. \n A single CertificateRef to a Kubernetes Secret has \"Core\" support. Implementations MAY choose to support attaching multiple certificates to a Listener, but this behavior is implementation-specific. \n References to a resource in different namespace are invalid UNLESS there is a ReferenceGrant in the target namespace that allows the certificate to be attached. If a ReferenceGrant does not allow this reference, the \"ResolvedRefs\" condition MUST be set to False for this listener with the \"RefNotPermitted\" reason. \n This field is required to have at least one element when the mode is set to \"Terminate\" (default) and is optional otherwise. \n CertificateRefs can reference to standard Kubernetes resources, i.e. Secret, or implementation-specific custom resources. \n Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls \n Support: Implementation-specific (More than one reference or other resource types)" + items: + description: "SecretObjectReference identifies an API object including its namespace, defaulting to Secret. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referenced object. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + mode: + default: Terminate + description: "Mode defines the TLS behavior for the TLS session initiated by the client. There are two possible modes: \n - Terminate: The TLS session between the downstream client and the Gateway is terminated at the Gateway. This mode requires certificateRefs to be set and contain at least one element. - Passthrough: The TLS session is NOT terminated by the Gateway. This implies that the Gateway can't decipher the TLS stream except for the ClientHello message of the TLS protocol. CertificateRefs field is ignored in this mode. \n Support: Core" + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Options are a list of key/value pairs to enable extended TLS configuration for each implementation. For example, configuring the minimum TLS version or supported cipher suites. \n A set of common keys MAY be defined by the API in the future. To avoid any ambiguity, implementation-specific definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. Un-prefixed names are reserved for key names defined by Gateway API. \n Support: Implementation-specific" + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs must be specified when TLSModeType is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) > 0 : true' + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must be specified for protocols ['HTTPS', 'TLS'] + rule: 'self.all(l, l.protocol in [''HTTPS'', ''TLS''] ? has(l.tls) : true)' + - message: tls must not be specified for protocols ['HTTP', 'TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? !has(l.tls) : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique for each listener + rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of Gateway. + properties: + addresses: + description: "Addresses lists the network addresses that have been bound to the Gateway. \n This list may differ from the addresses provided in the spec under some conditions: \n * no addresses are specified, all addresses are dynamically assigned * a combination of specified and dynamic addresses are assigned * a specified address was unusable (e.g. already in use) \n " + items: + description: GatewayStatusAddress describes a network address that is bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): true' + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: "Conditions describe the current conditions of the Gateway. \n Implementations should prefer to express Gateway conditions using the `GatewayConditionType` and `GatewayConditionReason` constants so that operators and tools can converge on a common vocabulary to describe Gateway state. \n Known condition types are: \n * \"Accepted\" * \"Programmed\" * \"Ready\"" + 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 }" + 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 + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: "AttachedRoutes represents the total number of Routes that have been successfully attached to this Listener. \n Successful attachment of a Route to a Listener is based solely on the combination of the AllowedRoutes field on the corresponding Listener and the Route's ParentRefs field. A Route is successfully attached to a Listener when it is selected by the Listener's AllowedRoutes field AND the Route has a valid ParentRef selecting the whole Gateway resource or a specific Listener as a parent resource (more detail on attachment semantics can be found in the documentation on the various Route kinds ParentRefs fields). Listener or Route status does not impact successful attachment, i.e. the AttachedRoutes field count MUST be set for Listeners with condition Accepted: false and MUST count successfully attached Routes that may themselves have Accepted: false conditions. \n Uses for this field include troubleshooting Route attachment and measuring blast radius/impact of changes to a Listener." + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this listener. + 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 }" + 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 + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + supportedKinds: + description: "SupportedKinds is the list indicating the Kinds supported by this listener. This MUST represent the kinds an implementation supports for that Listener configuration. \n If kinds are specified in Spec that are not supported, they MUST NOT appear in this list and an implementation MUST set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" reason. If both valid and invalid Route kinds are specified, the implementation MUST reference the valid Route kinds that have been specified." + items: + description: RouteGroupKind indicates the group and kind of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Gateway represents an instance of a service-traffic handling infrastructure by binding Listeners to a set of IP addresses. + 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: Spec defines the desired state of Gateway. + properties: + addresses: + description: "Addresses requested for this Gateway. This is optional and behavior can depend on the implementation. If a value is set in the spec and the requested address is invalid or unavailable, the implementation MUST indicate this in the associated entry in GatewayStatus.Addresses. \n The Addresses field represents a request for the address(es) on the \"outside of the Gateway\", that traffic bound for this Gateway will use. This could be the IP address or hostname of an external load balancer or other networking infrastructure, or some other address that traffic will be sent to. \n If no Addresses are specified, the implementation MAY schedule the Gateway in an implementation-specific manner, assigning an appropriate set of Addresses. \n The implementation MUST bind all Listeners to every GatewayAddress that it assigns to the Gateway and add a corresponding entry in GatewayStatus.Addresses. \n Support: Extended \n " + items: + description: GatewayAddress describes an address that can be bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: IPAddress values must be unique + rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, a2.type == a1.type && a2.value == a1.value) : true )' + - message: Hostname values must be unique + rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, a2.type == a1.type && a2.value == a1.value) : true )' + gatewayClassName: + description: GatewayClassName used for this Gateway. This is the name of a GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + infrastructure: + description: "Infrastructure defines infrastructure level attributes about this Gateway instance. \n Support: Core \n " + properties: + annotations: + additionalProperties: + description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Annotations that SHOULD be applied to any resources created in response to this Gateway. \n For implementations creating other Kubernetes objects, this should be the `metadata.annotations` field on resources. For other implementations, this refers to any relevant (implementation specific) \"annotations\" concepts. \n An implementation may chose to add additional implementation-specific annotations as they see fit. \n Support: Extended" + maxProperties: 8 + type: object + labels: + additionalProperties: + description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Labels that SHOULD be applied to any resources created in response to this Gateway. \n For implementations creating other Kubernetes objects, this should be the `metadata.labels` field on resources. For other implementations, this refers to any relevant (implementation specific) \"labels\" concepts. \n An implementation may chose to add additional implementation-specific labels as they see fit. \n Support: Extended" + maxProperties: 8 + type: object + type: object + listeners: + description: "Listeners associated with this Gateway. Listeners define logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. \n Each Listener in a set of Listeners (for example, in a single Gateway) MUST be _distinct_, in that a traffic flow MUST be able to be assigned to exactly one listener. (This section uses \"set of Listeners\" rather than \"Listeners in a single Gateway\" because implementations MAY merge configuration from multiple Gateways onto a single data plane, and these rules _also_ apply in that case). \n Practically, this means that each listener in a set MUST have a unique combination of Port, Protocol, and, if supported by the protocol, Hostname. \n Some combinations of port, protocol, and TLS settings are considered Core support and MUST be supported by implementations based on their targeted conformance profile: \n HTTP Profile \n 1. HTTPRoute, Port: 80, Protocol: HTTP 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided \n TLS Profile \n 1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough \n \"Distinct\" Listeners have the following property: \n The implementation can match inbound requests to a single distinct Listener. When multiple Listeners share values for fields (for example, two Listeners with the same Port value), the implementation can match requests to only one of the Listeners using other Listener fields. \n For example, the following Listener scenarios are distinct: \n 1. Multiple Listeners with the same Port that all use the \"HTTP\" Protocol that all have unique Hostname values. 2. Multiple Listeners with the same Port that use either the \"HTTPS\" or \"TLS\" Protocol that all have unique Hostname values. 3. A mixture of \"TCP\" and \"UDP\" Protocol Listeners, where no Listener with the same Protocol has the same Port value. \n Some fields in the Listener struct have possible values that affect whether the Listener is distinct. Hostname is particularly relevant for HTTP or HTTPS protocols. \n When using the Hostname value to select between same-Port, same-Protocol Listeners, the Hostname value must be different on each Listener for the Listener to be distinct. \n When the Listeners are distinct based on Hostname, inbound request hostnames MUST match from the most specific to least specific Hostname values to choose the correct Listener and its associated set of Routes. \n Exact matches must be processed before wildcard matches, and wildcard matches must be processed before fallback (empty Hostname value) matches. For example, `\"foo.example.com\"` takes precedence over `\"*.example.com\"`, and `\"*.example.com\"` takes precedence over `\"\"`. \n Additionally, if there are multiple wildcard entries, more specific wildcard entries must be processed before less specific wildcard entries. For example, `\"*.foo.example.com\"` takes precedence over `\"*.example.com\"`. The precise definition here is that the higher the number of dots in the hostname to the right of the wildcard character, the higher the precedence. \n The wildcard character will match any number of characters _and dots_ to the left, however, so `\"*.example.com\"` will match both `\"foo.bar.example.com\"` _and_ `\"bar.example.com\"`. \n If a set of Listeners contains Listeners that are not distinct, then those Listeners are Conflicted, and the implementation MUST set the \"Conflicted\" condition in the Listener Status to \"True\". \n Implementations MAY choose to accept a Gateway with some Conflicted Listeners only if they only accept the partial Listener set that contains no Conflicted Listeners. To put this another way, implementations may accept a partial Listener set only if they throw out *all* the conflicting Listeners. No picking one of the conflicting listeners as the winner. This also means that the Gateway must have at least one non-conflicting Listener in this case, otherwise it violates the requirement that at least one Listener must be present. \n The implementation MUST set a \"ListenersNotValid\" condition on the Gateway Status when the Gateway contains Conflicted Listeners whether or not they accept the Gateway. That Condition SHOULD clearly indicate in the Message which Listeners are conflicted, and which are Accepted. Additionally, the Listener status for those listeners SHOULD indicate which Listeners are conflicted and not Accepted. \n A Gateway's Listeners are considered \"compatible\" if: \n 1. They are distinct. 2. The implementation can serve them in compliance with the Addresses requirement that all Listeners are available on all assigned addresses. \n Compatible combinations in Extended support are expected to vary across implementations. A combination that is compatible for one implementation may not be compatible for another. \n For example, an implementation that cannot serve both TCP and UDP listeners on the same address, or cannot mix HTTPS and generic TLS listens on the same port would not consider those cases compatible, even though they are distinct. \n Note that requests SHOULD match at most one Listener. For example, if Listeners are defined for \"foo.example.com\" and \"*.example.com\", a request to \"foo.example.com\" SHOULD only be routed using routes attached to the \"foo.example.com\" Listener (and not the \"*.example.com\" Listener). This concept is known as \"Listener Isolation\". Implementations that do not support Listener Isolation MUST clearly document this. \n Implementations MAY merge separate Gateways onto a single set of Addresses if all Listeners across all Gateways are compatible. \n Support: Core" + items: + description: Listener embodies the concept of a logical endpoint where a Gateway accepts network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: "AllowedRoutes defines the types of routes that MAY be attached to a Listener and the trusted namespaces where those Route resources MAY be present. \n Although a client request may match multiple route rules, only one rule may ultimately receive the request. Matching precedence MUST be determined in order of the following criteria: \n * The most specific match as defined by the Route type. * The oldest Route based on creation timestamp. For example, a Route with a creation timestamp of \"2020-09-08 01:02:03\" is given precedence over a Route with a creation timestamp of \"2020-09-08 01:02:04\". * If everything else is equivalent, the Route appearing first in alphabetical order (namespace/name) should be given precedence. For example, foo/bar is given precedence over foo/baz. \n All valid rules within a Route attached to this Listener should be implemented. Invalid Route rules can be ignored (sometimes that will mean the full Route). If a Route rule transitions from valid to invalid, support for that Route rule should be dropped to ensure consistency. For example, even if a filter specified by a Route rule is invalid, the rest of the rules within that Route should still be supported. \n Support: Core" + properties: + kinds: + description: "Kinds specifies the groups and kinds of Routes that are allowed to bind to this Gateway Listener. When unspecified or empty, the kinds of Routes selected are determined using the Listener protocol. \n A RouteGroupKind MUST correspond to kinds of Routes that are compatible with the application protocol specified in the Listener's Protocol field. If an implementation does not support or recognize this resource type, it MUST set the \"ResolvedRefs\" condition to False for this Listener with the \"InvalidRouteKinds\" reason. \n Support: Core" + items: + description: RouteGroupKind indicates the group and kind of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: "Namespaces indicates namespaces from which Routes may be attached to this Listener. This is restricted to the namespace of this Gateway by default. \n Support: Core" + properties: + from: + default: Same + description: "From indicates where Routes will be selected for this Gateway. Possible values are: \n * All: Routes in all namespaces may be used by this Gateway. * Selector: Routes in namespaces selected by the selector may be used by this Gateway. * Same: Only Routes in the same namespace may be used by this Gateway. \n Support: Core" + enum: + - All + - Selector + - Same + type: string + selector: + description: "Selector must be specified when From is set to \"Selector\". In that case, only Routes in Namespaces matching this Selector will be selected by this Gateway. This field is ignored for other values of \"From\". \n Support: Core" + 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 + type: object + type: object + hostname: + description: "Hostname specifies the virtual hostname to match for protocol types that define this concept. When unspecified, all hostnames are matched. This field is ignored for protocols that don't require hostname based matching. \n Implementations MUST apply Hostname matching appropriately for each of the following protocols: \n * TLS: The Listener Hostname MUST match the SNI. * HTTP: The Listener Hostname MUST match the Host header of the request. * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP protocol layers as described above. If an implementation does not ensure that both the SNI and Host header match the Listener hostname, it MUST clearly document that. \n For HTTPRoute and TLSRoute resources, there is an interaction with the `spec.hostnames` array. When both listener and route specify hostnames, there MUST be an intersection between the values for a Route to be accepted. For more information, refer to the Route specific Hostnames documentation. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: "Name is the name of the Listener. This name MUST be unique within a Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: "Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules. \n Support: Core" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: "Protocol specifies the network protocol this listener expects to receive. \n Support: Core" + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zSA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: "TLS is the TLS configuration for the Listener. This field is required if the Protocol field is \"HTTPS\" or \"TLS\". It is invalid to set this field if the Protocol field is \"HTTP\", \"TCP\", or \"UDP\". \n The association of SNIs to Certificate defined in GatewayTLSConfig is defined based on the Hostname field for this listener. \n The GatewayClass MUST use the longest matching SNI out of all available certificates for any TLS handshake. \n Support: Core" + properties: + certificateRefs: + description: "CertificateRefs contains a series of references to Kubernetes objects that contains TLS certificates and private keys. These certificates are used to establish a TLS handshake for requests that match the hostname of the associated listener. \n A single CertificateRef to a Kubernetes Secret has \"Core\" support. Implementations MAY choose to support attaching multiple certificates to a Listener, but this behavior is implementation-specific. \n References to a resource in different namespace are invalid UNLESS there is a ReferenceGrant in the target namespace that allows the certificate to be attached. If a ReferenceGrant does not allow this reference, the \"ResolvedRefs\" condition MUST be set to False for this listener with the \"RefNotPermitted\" reason. \n This field is required to have at least one element when the mode is set to \"Terminate\" (default) and is optional otherwise. \n CertificateRefs can reference to standard Kubernetes resources, i.e. Secret, or implementation-specific custom resources. \n Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls \n Support: Implementation-specific (More than one reference or other resource types)" + items: + description: "SecretObjectReference identifies an API object including its namespace, defaulting to Secret. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid. \n References to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referenced object. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + mode: + default: Terminate + description: "Mode defines the TLS behavior for the TLS session initiated by the client. There are two possible modes: \n - Terminate: The TLS session between the downstream client and the Gateway is terminated at the Gateway. This mode requires certificateRefs to be set and contain at least one element. - Passthrough: The TLS session is NOT terminated by the Gateway. This implies that the Gateway can't decipher the TLS stream except for the ClientHello message of the TLS protocol. CertificateRefs field is ignored in this mode. \n Support: Core" + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: AnnotationValue is the value of an annotation in Gateway API. This is used for validation of maps such as TLS options. This roughly matches Kubernetes annotation validation, although the length validation in that case is based on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: "Options are a list of key/value pairs to enable extended TLS configuration for each implementation. For example, configuring the minimum TLS version or supported cipher suites. \n A set of common keys MAY be defined by the API in the future. To avoid any ambiguity, implementation-specific definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. Un-prefixed names are reserved for key names defined by Gateway API. \n Support: Implementation-specific" + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs must be specified when TLSModeType is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) > 0 : true' + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must be specified for protocols ['HTTPS', 'TLS'] + rule: 'self.all(l, l.protocol in [''HTTPS'', ''TLS''] ? has(l.tls) : true)' + - message: tls must not be specified for protocols ['HTTP', 'TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? !has(l.tls) : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique for each listener + rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of Gateway. + properties: + addresses: + description: "Addresses lists the network addresses that have been bound to the Gateway. \n This list may differ from the addresses provided in the spec under some conditions: \n * no addresses are specified, all addresses are dynamically assigned * a combination of specified and dynamic addresses are assigned * a specified address was unusable (e.g. already in use) \n " + items: + description: GatewayStatusAddress describes a network address that is bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: "Value of the address. The validity of the values will depend on the type and support by the controller. \n Examples: `1.2.3.4`, `128::1`, `my-ip-address`." + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): true' + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: "Conditions describe the current conditions of the Gateway. \n Implementations should prefer to express Gateway conditions using the `GatewayConditionType` and `GatewayConditionReason` constants so that operators and tools can converge on a common vocabulary to describe Gateway state. \n Known condition types are: \n * \"Accepted\" * \"Programmed\" * \"Ready\"" + 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 }" + 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 + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: "AttachedRoutes represents the total number of Routes that have been successfully attached to this Listener. \n Successful attachment of a Route to a Listener is based solely on the combination of the AllowedRoutes field on the corresponding Listener and the Route's ParentRefs field. A Route is successfully attached to a Listener when it is selected by the Listener's AllowedRoutes field AND the Route has a valid ParentRef selecting the whole Gateway resource or a specific Listener as a parent resource (more detail on attachment semantics can be found in the documentation on the various Route kinds ParentRefs fields). Listener or Route status does not impact successful attachment, i.e. the AttachedRoutes field count MUST be set for Listeners with condition Accepted: false and MUST count successfully attached Routes that may themselves have Accepted: false conditions. \n Uses for this field include troubleshooting Route attachment and measuring blast radius/impact of changes to a Listener." + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this listener. + 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 }" + 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 + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + supportedKinds: + description: "SupportedKinds is the list indicating the Kinds supported by this listener. This MUST represent the kinds an implementation supports for that Listener configuration. \n If kinds are specified in Spec that are not supported, they MUST NOT appear in this list and an implementation MUST set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" reason. If both valid and invalid Route kinds are specified, the implementation MUST reference the valid Route kinds that have been specified." + items: + description: RouteGroupKind indicates the group and kind of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_grpcroutes.yaml b/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_grpcroutes.yaml new file mode 100644 index 000000000..1d2964156 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_grpcroutes.yaml @@ -0,0 +1,819 @@ +# +# config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: grpcroutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GRPCRoute + listKind: GRPCRouteList + plural: grpcroutes + singular: grpcroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: "GRPCRoute provides a way to route gRPC requests. This includes the capability to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header. Filters can be used to specify additional processing steps. Backends specify where matching requests will be routed. \n GRPCRoute falls under extended support within the Gateway API. Within the following specification, the word \"MUST\" indicates that an implementation supporting GRPCRoute must conform to the indicated requirement, but an implementation not supporting this route type need not follow the requirement unless explicitly indicated. \n Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via ALPN. If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1. \n Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST support HTTP/2 over cleartext TCP (h2c, https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial upgrade from HTTP/1.1, i.e. with prior knowledge (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1, i.e. without prior knowledge." + 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: Spec defines the desired state of GRPCRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostnames to match against the GRPC Host header to select a GRPCRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label MUST appear by itself as the first label. \n If a hostname is specified by both the Listener and GRPCRoute, there MUST be at least one intersecting hostname for the GRPCRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `test.example.com` and `*.example.com` would both match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and GRPCRoute have specified hostnames, any GRPCRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the GRPCRoute specified `test.example.com` and `test.example.net`, `test.example.net` MUST NOT be considered for a match. \n If both the Listener and GRPCRoute have specified hostnames, and none match with the criteria above, then the GRPCRoute MUST NOT be accepted by the implementation. The implementation MUST raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n If a Route (A) of type HTTPRoute or GRPCRoute is attached to a Listener and that listener already has another Route (B) of the other type attached and the intersection of the hostnames of A and B is non-empty, then the implementation MUST accept exactly one of these two routes, determined by the following criteria, in order: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n The rejected Route MUST raise an 'Accepted' condition with a status of 'False' in the corresponding RouteParentStatus. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. For Services, that means the Service must either be in the same namespace for a \"producer\" route, or the mesh implementation must support and allow \"consumer\" routes for the referenced Service. ReferenceGrant is not applicable for governing ParentRefs to Services - it is not possible to create a \"producer\" route for a Service in a different namespace from the Route. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) This API may be extended in the future to support additional kinds of parent resources. \n ParentRefs must be _distinct_. This means either that: \n * They select different objects. If this is the case, then parentRef entries are distinct. In terms of fields, this means that the multi-part key defined by `group`, `kind`, `namespace`, and `name` must be unique across all parentRef entries in the Route. * They do not select different objects, but for each optional field used, each ParentRef that selects the same object must set the same set of optional fields to different values. If one ParentRef sets a combination of optional fields, all must set the same combination. \n Some examples: \n * If one ParentRef sets `sectionName`, all ParentRefs referencing the same object must also set `sectionName`. * If one ParentRef sets `port`, all ParentRefs referencing the same object must also set `port`. * If one ParentRef sets `sectionName` and `port`, all ParentRefs referencing the same object must also set `sectionName` and `port`. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable other kinds of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n " + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n This API may be extended in the future to support additional kinds of parent resources. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) + rules: + description: Rules are a list of GRPC matchers, filters and actions. + items: + description: GRPCRouteRule defines the semantics for matching a gRPC request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive an `UNAVAILABLE` status. \n See the GRPCBackendRef definition for the rules about what makes a single GRPCBackendRef invalid. \n When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive an `UNAVAILABLE` status. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" + items: + description: "GRPCBackendRef defines how a GRPCRoute forwards a gRPC request. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n \n When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. \n Implementations supporting appProtocol SHOULD recognize the Kubernetes Standard Application Protocols defined in KEP-3726. \n If a Service appProtocol isn't specified, an implementation MAY infer the backend protocol through its own means. Implementations MAY infer the protocol from the Route type referring to the backend Service. \n If a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason. \n " + properties: + filters: + description: "Filters defined at this level MUST be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in GRPCRouteRule.)" + items: + description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific \n This filter can be used multiple times within the same rule." + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n This filter can be used multiple times within the same rule. Note that not all implementations will be able to support mirroring to multiple backends. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n Mirrored requests must be sent only to a single destination endpoint within this BackendRef, irrespective of how many endpoints are present within this BackendRef. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + required: + - backendRef + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.extensionRef must be nil if the filter.type is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() <= 1 + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations that support GRPCRoute. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying the same filter multiple times is not supported unless explicitly indicated in the filter. \n If an implementation can not support a combination of filters, it must clearly document that limitation. In cases where incompatible or unsupported filters are specified and cause the `Accepted` condition to be set to status `False`, implementations may use the `IncompatibleFilters` reason to specify this configuration error. \n Support: Core" + items: + description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific \n This filter can be used multiple times within the same rule." + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n This filter can be used multiple times within the same rule. Note that not all implementations will be able to support mirroring to multiple backends. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n Mirrored requests must be sent only to a single destination endpoint within this BackendRef, irrespective of how many endpoints are present within this BackendRef. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + required: + - backendRef + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.extensionRef must be nil if the filter.type is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() <= 1 + matches: + description: "Matches define conditions used for matching the rule against incoming gRPC requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - method: service: foo.bar headers: values: version: 2 - method: service: foo.bar.v2 ``` \n For a request to match against this rule, it MUST satisfy EITHER of the two conditions: \n - service of foo.bar AND contains the header `version: 2` - service of foo.bar.v2 \n See the documentation for GRPCRouteMatch on how to specify multiple match conditions to be ANDed together. \n If no matches are specified, the implementation MUST match every gRPC request. \n Proxy or Load Balancer routing configuration generated from GRPCRoutes MUST prioritize rules based on the following criteria, continuing on ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes. Precedence MUST be given to the rule with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. * Characters in a matching service. * Characters in a matching method. * Header matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within the Route that has been given precedence, matching precedence MUST be granted to the first matching rule meeting the above criteria." + items: + description: "GRPCRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a gRPC request only if its service is `foo` AND it contains the `version: v1` header: \n ``` matches: - method: type: Exact service: \"foo\" headers: - name: \"version\" value \"v1\" \n ```" + properties: + headers: + description: Headers specifies gRPC request header matchers. Multiple match values are ANDed together, meaning, a request MUST match all the specified headers to select the route. + items: + description: GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request headers. + properties: + name: + description: "Name is the name of the gRPC Header to be matched. \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: Type specifies how to match against the value of the header. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of the gRPC Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: Method specifies a gRPC request service/method matcher. If this field is not specified, all services and methods will match. + properties: + method: + description: "Value of the method to match against. If left empty or omitted, will match all services. \n At least one of Service and Method MUST be a non-empty string." + maxLength: 1024 + type: string + service: + description: "Value of the service to match against. If left empty or omitted, will match any service. \n At least one of Service and Method MUST be a non-empty string." + maxLength: 1024 + type: string + type: + default: Exact + description: "Type specifies how to match against the service and/or method. Support: Core (Exact with service and method specified) \n Support: Implementation-specific (Exact with method specified but no service specified) \n Support: Implementation-specific (RegularExpression)" + enum: + - Exact + - RegularExpression + type: string + type: object + x-kubernetes-validations: + - message: One or both of 'service' or 'method' must be specified + rule: 'has(self.type) ? has(self.service) || has(self.method) : true' + - message: service must only contain valid characters (matching ^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$) + rule: '(!has(self.type) || self.type == ''Exact'') && has(self.service) ? self.service.matches(r"""^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$"""): true' + - message: method must only contain valid characters (matching ^[A-Za-z_][A-Za-z_0-9]*$) + rule: '(!has(self.type) || self.type == ''Exact'') && has(self.method) ? self.method.matches(r"""^[A-Za-z_][A-Za-z_0-9]*$"""): true' + type: object + maxItems: 8 + type: array + type: object + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of GRPCRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + 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 }" + 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 + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_httproutes.yaml b/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_httproutes.yaml new file mode 100644 index 000000000..5ce7697b4 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_httproutes.yaml @@ -0,0 +1,2263 @@ +# +# config/crd/experimental/gateway.networking.k8s.io_httproutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: httproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: HTTPRoute + listKind: HTTPRouteList + plural: httproutes + singular: httproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. + 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: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostnames that should match against the HTTP Host header to select a HTTPRoute used to process the request. Implementations MUST ignore any port value specified in the HTTP Host header while performing a match and (absent of any applicable header modification configuration) MUST forward this header unmodified to the backend. \n Valid values for Hostnames are determined by RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. For Services, that means the Service must either be in the same namespace for a \"producer\" route, or the mesh implementation must support and allow \"consumer\" routes for the referenced Service. ReferenceGrant is not applicable for governing ParentRefs to Services - it is not possible to create a \"producer\" route for a Service in a different namespace from the Route. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) This API may be extended in the future to support additional kinds of parent resources. \n ParentRefs must be _distinct_. This means either that: \n * They select different objects. If this is the case, then parentRef entries are distinct. In terms of fields, this means that the multi-part key defined by `group`, `kind`, `namespace`, and `name` must be unique across all parentRef entries in the Route. * They do not select different objects, but for each optional field used, each ParentRef that selects the same object must set the same set of optional fields to different values. If one ParentRef sets a combination of optional fields, all must set the same combination. \n Some examples: \n * If one ParentRef sets `sectionName`, all ParentRefs referencing the same object must also set `sectionName`. * If one ParentRef sets `port`, all ParentRefs referencing the same object must also set `port`. * If one ParentRef sets `sectionName` and `port`, all ParentRefs referencing the same object must also set `sectionName` and `port`. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable other kinds of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n " + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n This API may be extended in the future to support additional kinds of parent resources. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Extended for Kubernetes ServiceImport \n Support: Implementation-specific for any other resource \n Support for weight: Core" + items: + description: "HTTPBackendRef defines how a HTTPRoute forwards a HTTP request. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n \n When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. \n Implementations supporting appProtocol SHOULD recognize the Kubernetes Standard Application Protocols defined in KEP-3726. \n If a Service appProtocol isn't specified, an implementation MAY infer the backend protocol through its own means. Implementations MAY infer the protocol from the Route type referring to the backend Service. \n If a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason. \n " + properties: + filters: + description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n This filter can be used multiple times within the same rule. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n This filter can be used multiple times within the same rule. Note that not all implementations will be able to support mirroring to multiple backends. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n Mirrored requests must be sent only to a single destination endpoint within this BackendRef, irrespective of how many endpoints are present within this BackendRef. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname in the `Host` header of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch of \"/xyz\" would be modified to \"/xyz/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in the implementation setting the Accepted Condition for the Route to `status: False`. \n Request Path | Prefix Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- /foo/bar | /foo | /xyz | /xyz/bar /foo/bar | /foo | /xyz/ | /xyz/bar /foo/bar | /foo/ | /xyz | /xyz/bar /foo/bar | /foo/ | /xyz/ | /xyz/bar /foo | /foo | /xyz | /xyz /foo/ | /foo | /xyz | /xyz/ /foo/bar | /foo | | /bar /foo/ | /foo | | / /foo | /foo | | / /foo/ | /foo | / | / /foo | /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type == ''ReplacePrefixMatch'' : true' + port: + description: "Port is the port to be used in the value of the `Location` header in the response. \n If no port is specified, the redirect port MUST be derived using the following rules: \n * If redirect scheme is not-empty, the redirect port MUST be the well-known port associated with the redirect scheme. Specifically \"http\" to port 80 and \"https\" to port 443. If the redirect scheme does not have a well-known port, the listener port of the Gateway SHOULD be used. * If redirect scheme is empty, the redirect port MUST be the Gateway Listener port. \n Implementations SHOULD NOT add the port number in the 'Location' header in the following cases: \n * A Location header that will use HTTP (whether that is determined via the Listener protocol or the Scheme field) _and_ use port 80. * A Location header that will use HTTPS (whether that is determined via the Listener protocol or the Scheme field) _and_ use port 443. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Scheme redirects can affect the port of the redirect, for more information, refer to the documentation for the port field of this filter. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended" + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch of \"/xyz\" would be modified to \"/xyz/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in the implementation setting the Accepted Condition for the Route to `status: False`. \n Request Path | Prefix Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- /foo/bar | /foo | /xyz | /xyz/bar /foo/bar | /foo | /xyz/ | /xyz/bar /foo/bar | /foo/ | /xyz | /xyz/bar /foo/bar | /foo/ | /xyz/ | /xyz/bar /foo | /foo | /xyz | /xyz /foo/ | /foo | /xyz | /xyz/ /foo/bar | /foo | | /bar /foo/ | /foo | | / /foo | /foo | | / /foo/ | /foo | / | / /foo | /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && self.exists(f, f.type == ''URLRewrite''))' + - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying the same filter multiple times is not supported unless explicitly indicated in the filter. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In cases where incompatible or unsupported filters are specified and cause the `Accepted` condition to be set to status `False`, implementations may use the `IncompatibleFilters` reason to specify this configuration error. \n Support: Core" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n This filter can be used multiple times within the same rule. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n This filter can be used multiple times within the same rule. Note that not all implementations will be able to support mirroring to multiple backends. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n Mirrored requests must be sent only to a single destination endpoint within this BackendRef, irrespective of how many endpoints are present within this BackendRef. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname in the `Host` header of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch of \"/xyz\" would be modified to \"/xyz/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in the implementation setting the Accepted Condition for the Route to `status: False`. \n Request Path | Prefix Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- /foo/bar | /foo | /xyz | /xyz/bar /foo/bar | /foo | /xyz/ | /xyz/bar /foo/bar | /foo/ | /xyz | /xyz/bar /foo/bar | /foo/ | /xyz/ | /xyz/bar /foo | /foo | /xyz | /xyz /foo/ | /foo | /xyz | /xyz/ /foo/bar | /foo | | /bar /foo/ | /foo | | / /foo | /foo | | / /foo/ | /foo | / | / /foo | /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type == ''ReplacePrefixMatch'' : true' + port: + description: "Port is the port to be used in the value of the `Location` header in the response. \n If no port is specified, the redirect port MUST be derived using the following rules: \n * If redirect scheme is not-empty, the redirect port MUST be the well-known port associated with the redirect scheme. Specifically \"http\" to port 80 and \"https\" to port 443. If the redirect scheme does not have a well-known port, the listener port of the Gateway SHOULD be used. * If redirect scheme is empty, the redirect port MUST be the Gateway Listener port. \n Implementations SHOULD NOT add the port number in the 'Location' header in the following cases: \n * A Location header that will use HTTP (whether that is determined via the Listener protocol or the Scheme field) _and_ use port 80. * A Location header that will use HTTPS (whether that is determined via the Listener protocol or the Scheme field) _and_ use port 443. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Scheme redirects can affect the port of the redirect, for more information, refer to the documentation for the port field of this filter. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended" + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch of \"/xyz\" would be modified to \"/xyz/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in the implementation setting the Accepted Condition for the Route to `status: False`. \n Request Path | Prefix Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- /foo/bar | /foo | /xyz | /xyz/bar /foo/bar | /foo | /xyz/ | /xyz/bar /foo/bar | /foo/ | /xyz | /xyz/bar /foo/bar | /foo/ | /xyz/ | /xyz/bar /foo | /foo | /xyz | /xyz /foo/ | /foo | /xyz | /xyz/ /foo/bar | /foo | | /bar /foo/ | /foo | | / /foo | /foo | | / /foo/ | /foo | / | / /foo | /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 + matches: + default: + - path: + type: PathPrefix + value: / + description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match having: \n * \"Exact\" path match. * \"Prefix\" path match with largest number of characters. * Method match. * Largest number of header matches. * Largest number of query param matches. \n Note: The precedence of RegularExpression path matches are implementation-specific. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." + items: + description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n path: value: \"/foo\" headers: - name: \"version\" value \"v1\" \n ```" + properties: + headers: + description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. + items: + description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute path and start with '/' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'') : true' + - message: must not contain '//' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'') : true' + - message: must not contain '/./' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'') : true' + - message: must not contain '/../' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'') : true' + - message: must not contain '%2f' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'') : true' + - message: must not contain '%2F' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'') : true' + - message: must not contain '#' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'') : true' + - message: must not end with '/..' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'') : true' + - message: must not end with '/.' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'') : true' + - message: type must be one of ['Exact', 'PathPrefix', 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] || self.type == 'RegularExpression' + - message: must only contain valid characters (matching ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") : true' + queryParams: + description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" + items: + description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. + properties: + name: + description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 8 + type: array + timeouts: + description: "Timeouts defines the timeouts that can be configured for an HTTP request. \n Support: Extended \n " + properties: + backendRequest: + description: "BackendRequest specifies a timeout for an individual request from the gateway to a backend. This covers the time from when the request first starts being sent from the gateway to when the full response has been received from the backend. \n An entire client HTTP transaction with a gateway, covered by the Request timeout, may result in more than one call from the gateway to the destination backend, for example, if automatic retries are supported. \n Because the Request timeout encompasses the BackendRequest timeout, the value of BackendRequest must be <= the value of Request timeout. \n Support: Extended" + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + request: + description: "Request specifies the maximum duration for a gateway to respond to an HTTP request. If the gateway has not been able to respond before this deadline is met, the gateway MUST return a timeout error. \n For example, setting the `rules.timeouts.request` field to the value `10s` in an `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds to complete. \n This timeout is intended to cover as close to the whole request-response transaction as possible although an implementation MAY choose to start the timeout after the entire request stream has been received instead of immediately after the transaction is initiated by the client. \n When this field is unspecified, request timeout behavior is implementation-specific. \n Support: Extended" + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + x-kubernetes-validations: + - message: backendRequest timeout cannot be longer than request timeout + rule: '!(has(self.request) && has(self.backendRequest) && duration(self.request) != duration(''0s'') && duration(self.backendRequest) > duration(self.request))' + type: object + x-kubernetes-validations: + - message: RequestRedirect filter must not be used together with backendRefs + rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ? (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))): true' + - message: When using RequestRedirect filter with path.replacePrefixMatch, exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect) && has(f.requestRedirect.path) && f.requestRedirect.path.type == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) ? ((size(self.matches) != 1 || !has(self.matches[0].path) || self.matches[0].path.type != ''PathPrefix'') ? false : true) : true' + - message: When using URLRewrite filter with path.replacePrefixMatch, exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite) && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches) != 1 || !has(self.matches[0].path) || self.matches[0].path.type != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, when using RequestRedirect filter with path.replacePrefixMatch, exactly one PathPrefix match must be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect) && has(f.requestRedirect.path) && f.requestRedirect.path.type == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) || self.matches[0].path.type != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, When using URLRewrite filter with path.replacePrefixMatch, exactly one PathPrefix match must be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite) && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) || self.matches[0].path.type != ''PathPrefix'') ? false : true) : true' + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + 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 }" + 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 + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. + 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: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostnames that should match against the HTTP Host header to select a HTTPRoute used to process the request. Implementations MUST ignore any port value specified in the HTTP Host header while performing a match and (absent of any applicable header modification configuration) MUST forward this header unmodified to the backend. \n Valid values for Hostnames are determined by RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. For Services, that means the Service must either be in the same namespace for a \"producer\" route, or the mesh implementation must support and allow \"consumer\" routes for the referenced Service. ReferenceGrant is not applicable for governing ParentRefs to Services - it is not possible to create a \"producer\" route for a Service in a different namespace from the Route. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) This API may be extended in the future to support additional kinds of parent resources. \n ParentRefs must be _distinct_. This means either that: \n * They select different objects. If this is the case, then parentRef entries are distinct. In terms of fields, this means that the multi-part key defined by `group`, `kind`, `namespace`, and `name` must be unique across all parentRef entries in the Route. * They do not select different objects, but for each optional field used, each ParentRef that selects the same object must set the same set of optional fields to different values. If one ParentRef sets a combination of optional fields, all must set the same combination. \n Some examples: \n * If one ParentRef sets `sectionName`, all ParentRefs referencing the same object must also set `sectionName`. * If one ParentRef sets `port`, all ParentRefs referencing the same object must also set `port`. * If one ParentRef sets `sectionName` and `port`, all ParentRefs referencing the same object must also set `sectionName` and `port`. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable other kinds of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n " + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n This API may be extended in the future to support additional kinds of parent resources. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Extended for Kubernetes ServiceImport \n Support: Implementation-specific for any other resource \n Support for weight: Core" + items: + description: "HTTPBackendRef defines how a HTTPRoute forwards a HTTP request. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n \n When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. \n Implementations supporting appProtocol SHOULD recognize the Kubernetes Standard Application Protocols defined in KEP-3726. \n If a Service appProtocol isn't specified, an implementation MAY infer the backend protocol through its own means. Implementations MAY infer the protocol from the Route type referring to the backend Service. \n If a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason. \n " + properties: + filters: + description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n This filter can be used multiple times within the same rule. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n This filter can be used multiple times within the same rule. Note that not all implementations will be able to support mirroring to multiple backends. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n Mirrored requests must be sent only to a single destination endpoint within this BackendRef, irrespective of how many endpoints are present within this BackendRef. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname in the `Host` header of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch of \"/xyz\" would be modified to \"/xyz/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in the implementation setting the Accepted Condition for the Route to `status: False`. \n Request Path | Prefix Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- /foo/bar | /foo | /xyz | /xyz/bar /foo/bar | /foo | /xyz/ | /xyz/bar /foo/bar | /foo/ | /xyz | /xyz/bar /foo/bar | /foo/ | /xyz/ | /xyz/bar /foo | /foo | /xyz | /xyz /foo/ | /foo | /xyz | /xyz/ /foo/bar | /foo | | /bar /foo/ | /foo | | / /foo | /foo | | / /foo/ | /foo | / | / /foo | /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type == ''ReplacePrefixMatch'' : true' + port: + description: "Port is the port to be used in the value of the `Location` header in the response. \n If no port is specified, the redirect port MUST be derived using the following rules: \n * If redirect scheme is not-empty, the redirect port MUST be the well-known port associated with the redirect scheme. Specifically \"http\" to port 80 and \"https\" to port 443. If the redirect scheme does not have a well-known port, the listener port of the Gateway SHOULD be used. * If redirect scheme is empty, the redirect port MUST be the Gateway Listener port. \n Implementations SHOULD NOT add the port number in the 'Location' header in the following cases: \n * A Location header that will use HTTP (whether that is determined via the Listener protocol or the Scheme field) _and_ use port 80. * A Location header that will use HTTPS (whether that is determined via the Listener protocol or the Scheme field) _and_ use port 443. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Scheme redirects can affect the port of the redirect, for more information, refer to the documentation for the port field of this filter. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended" + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch of \"/xyz\" would be modified to \"/xyz/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in the implementation setting the Accepted Condition for the Route to `status: False`. \n Request Path | Prefix Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- /foo/bar | /foo | /xyz | /xyz/bar /foo/bar | /foo | /xyz/ | /xyz/bar /foo/bar | /foo/ | /xyz | /xyz/bar /foo/bar | /foo/ | /xyz/ | /xyz/bar /foo | /foo | /xyz | /xyz /foo/ | /foo | /xyz | /xyz/ /foo/bar | /foo | | /bar /foo/ | /foo | | / /foo | /foo | | / /foo/ | /foo | / | / /foo | /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && self.exists(f, f.type == ''URLRewrite''))' + - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying the same filter multiple times is not supported unless explicitly indicated in the filter. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In cases where incompatible or unsupported filters are specified and cause the `Accepted` condition to be set to status `False`, implementations may use the `IncompatibleFilters` reason to specify this configuration error. \n Support: Core" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n This filter can be used multiple times within the same rule. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n This filter can be used multiple times within the same rule. Note that not all implementations will be able to support mirroring to multiple backends. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n Mirrored requests must be sent only to a single destination endpoint within this BackendRef, irrespective of how many endpoints are present within this BackendRef. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname in the `Host` header of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch of \"/xyz\" would be modified to \"/xyz/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in the implementation setting the Accepted Condition for the Route to `status: False`. \n Request Path | Prefix Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- /foo/bar | /foo | /xyz | /xyz/bar /foo/bar | /foo | /xyz/ | /xyz/bar /foo/bar | /foo/ | /xyz | /xyz/bar /foo/bar | /foo/ | /xyz/ | /xyz/bar /foo | /foo | /xyz | /xyz /foo/ | /foo | /xyz | /xyz/ /foo/bar | /foo | | /bar /foo/ | /foo | | / /foo | /foo | | / /foo/ | /foo | / | / /foo | /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type == ''ReplacePrefixMatch'' : true' + port: + description: "Port is the port to be used in the value of the `Location` header in the response. \n If no port is specified, the redirect port MUST be derived using the following rules: \n * If redirect scheme is not-empty, the redirect port MUST be the well-known port associated with the redirect scheme. Specifically \"http\" to port 80 and \"https\" to port 443. If the redirect scheme does not have a well-known port, the listener port of the Gateway SHOULD be used. * If redirect scheme is empty, the redirect port MUST be the Gateway Listener port. \n Implementations SHOULD NOT add the port number in the 'Location' header in the following cases: \n * A Location header that will use HTTP (whether that is determined via the Listener protocol or the Scheme field) _and_ use port 80. * A Location header that will use HTTPS (whether that is determined via the Listener protocol or the Scheme field) _and_ use port 443. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Scheme redirects can affect the port of the redirect, for more information, refer to the documentation for the port field of this filter. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended" + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended" + properties: + replaceFullPath: + description: ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch of \"/xyz\" would be modified to \"/xyz/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in the implementation setting the Accepted Condition for the Route to `status: False`. \n Request Path | Prefix Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- /foo/bar | /foo | /xyz | /xyz/bar /foo/bar | /foo | /xyz/ | /xyz/bar /foo/bar | /foo/ | /xyz | /xyz/bar /foo/bar | /foo/ | /xyz/ | /xyz/bar /foo | /foo | /xyz | /xyz /foo/ | /foo | /xyz | /xyz/ /foo/bar | /foo | | /bar /foo/ | /foo | | / /foo | /foo | | / /foo/ | /foo | / | / /foo | /foo | / | /" + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`." + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 + matches: + default: + - path: + type: PathPrefix + value: / + description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match having: \n * \"Exact\" path match. * \"Prefix\" path match with largest number of characters. * Method match. * Largest number of header matches. * Largest number of query param matches. \n Note: The precedence of RegularExpression path matches are implementation-specific. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." + items: + description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n path: value: \"/foo\" headers: - name: \"version\" value \"v1\" \n ```" + properties: + headers: + description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. + items: + description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute path and start with '/' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'') : true' + - message: must not contain '//' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'') : true' + - message: must not contain '/./' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'') : true' + - message: must not contain '/../' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'') : true' + - message: must not contain '%2f' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'') : true' + - message: must not contain '%2F' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'') : true' + - message: must not contain '#' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'') : true' + - message: must not end with '/..' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'') : true' + - message: must not end with '/.' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'') : true' + - message: type must be one of ['Exact', 'PathPrefix', 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] || self.type == 'RegularExpression' + - message: must only contain valid characters (matching ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") : true' + queryParams: + description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" + items: + description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. + properties: + name: + description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 8 + type: array + timeouts: + description: "Timeouts defines the timeouts that can be configured for an HTTP request. \n Support: Extended \n " + properties: + backendRequest: + description: "BackendRequest specifies a timeout for an individual request from the gateway to a backend. This covers the time from when the request first starts being sent from the gateway to when the full response has been received from the backend. \n An entire client HTTP transaction with a gateway, covered by the Request timeout, may result in more than one call from the gateway to the destination backend, for example, if automatic retries are supported. \n Because the Request timeout encompasses the BackendRequest timeout, the value of BackendRequest must be <= the value of Request timeout. \n Support: Extended" + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + request: + description: "Request specifies the maximum duration for a gateway to respond to an HTTP request. If the gateway has not been able to respond before this deadline is met, the gateway MUST return a timeout error. \n For example, setting the `rules.timeouts.request` field to the value `10s` in an `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds to complete. \n This timeout is intended to cover as close to the whole request-response transaction as possible although an implementation MAY choose to start the timeout after the entire request stream has been received instead of immediately after the transaction is initiated by the client. \n When this field is unspecified, request timeout behavior is implementation-specific. \n Support: Extended" + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + x-kubernetes-validations: + - message: backendRequest timeout cannot be longer than request timeout + rule: '!(has(self.request) && has(self.backendRequest) && duration(self.request) != duration(''0s'') && duration(self.backendRequest) > duration(self.request))' + type: object + x-kubernetes-validations: + - message: RequestRedirect filter must not be used together with backendRefs + rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ? (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))): true' + - message: When using RequestRedirect filter with path.replacePrefixMatch, exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect) && has(f.requestRedirect.path) && f.requestRedirect.path.type == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) ? ((size(self.matches) != 1 || !has(self.matches[0].path) || self.matches[0].path.type != ''PathPrefix'') ? false : true) : true' + - message: When using URLRewrite filter with path.replacePrefixMatch, exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite) && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches) != 1 || !has(self.matches[0].path) || self.matches[0].path.type != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, when using RequestRedirect filter with path.replacePrefixMatch, exactly one PathPrefix match must be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect) && has(f.requestRedirect.path) && f.requestRedirect.path.type == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) || self.matches[0].path.type != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, When using URLRewrite filter with path.replacePrefixMatch, exactly one PathPrefix match must be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite) && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) || self.matches[0].path.type != ''PathPrefix'') ? false : true) : true' + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + 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 }" + 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 + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_referencegrants.yaml b/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_referencegrants.yaml new file mode 100644 index 000000000..fb9a6dc1e --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_referencegrants.yaml @@ -0,0 +1,205 @@ +# +# config/crd/experimental/gateway.networking.k8s.io_referencegrants.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: referencegrants.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: ReferenceGrant + listKind: ReferenceGrantList + plural: referencegrants + shortNames: + - refgrant + singular: referencegrant + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + deprecated: true + deprecationWarning: The v1alpha2 version of ReferenceGrant has been deprecated and will be removed in a future release of the API. Please upgrade to v1beta1. + name: v1alpha2 + schema: + openAPIV3Schema: + description: "ReferenceGrant identifies kinds of resources in other namespaces that are trusted to reference the specified kinds of resources in the same namespace as the policy. \n Each ReferenceGrant can be used to represent a unique trust relationship. Additional Reference Grants can be used to add to the set of trusted sources of inbound references for the namespace they are defined within. \n A ReferenceGrant is required for all cross-namespace references in Gateway API (with the exception of cross-namespace Route-Gateway attachment, which is governed by the AllowedRoutes configuration on the Gateway, and cross-namespace Service ParentRefs on a \"consumer\" mesh Route, which defines routing rules applicable only to workloads in the Route namespace). ReferenceGrants allowing a reference from a Route to a Service are only applicable to BackendRefs. \n ReferenceGrant is a form of runtime verification allowing users to assert which cross-namespace object references are permitted. Implementations that support ReferenceGrant MUST NOT permit cross-namespace references which have no grant, and MUST respond to the removal of a grant by revoking the access that the grant allowed." + 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: Spec defines the desired state of ReferenceGrant. + properties: + from: + description: "From describes the trusted namespaces and kinds that can reference the resources described in \"To\". Each entry in this list MUST be considered to be an additional place that references can be valid from, or to put this another way, entries MUST be combined using OR. \n Support: Core" + items: + description: ReferenceGrantFrom describes trusted namespaces and kinds. + properties: + group: + description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field. \n When used to permit a SecretObjectReference: \n * Gateway \n When used to permit a BackendObjectReference: \n * GRPCRoute * HTTPRoute * TCPRoute * TLSRoute * UDPRoute" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + namespace: + description: "Namespace is the namespace of the referent. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - namespace + type: object + maxItems: 16 + minItems: 1 + type: array + to: + description: "To describes the resources that may be referenced by the resources described in \"From\". Each entry in this list MUST be considered to be an additional place that references can be valid to, or to put this another way, entries MUST be combined using OR. \n Support: Core" + items: + description: ReferenceGrantTo describes what Kinds are allowed as targets of the references. + properties: + group: + description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field: \n * Secret when used to permit a SecretObjectReference * Service when used to permit a BackendObjectReference" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. When unspecified, this policy refers to all resources of the specified Group and Kind in the local namespace. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - from + - to + type: object + type: object + served: true + storage: false + subresources: {} + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: "ReferenceGrant identifies kinds of resources in other namespaces that are trusted to reference the specified kinds of resources in the same namespace as the policy. \n Each ReferenceGrant can be used to represent a unique trust relationship. Additional Reference Grants can be used to add to the set of trusted sources of inbound references for the namespace they are defined within. \n All cross-namespace references in Gateway API (with the exception of cross-namespace Gateway-route attachment) require a ReferenceGrant. \n ReferenceGrant is a form of runtime verification allowing users to assert which cross-namespace object references are permitted. Implementations that support ReferenceGrant MUST NOT permit cross-namespace references which have no grant, and MUST respond to the removal of a grant by revoking the access that the grant allowed." + 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: Spec defines the desired state of ReferenceGrant. + properties: + from: + description: "From describes the trusted namespaces and kinds that can reference the resources described in \"To\". Each entry in this list MUST be considered to be an additional place that references can be valid from, or to put this another way, entries MUST be combined using OR. \n Support: Core" + items: + description: ReferenceGrantFrom describes trusted namespaces and kinds. + properties: + group: + description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field. \n When used to permit a SecretObjectReference: \n * Gateway \n When used to permit a BackendObjectReference: \n * GRPCRoute * HTTPRoute * TCPRoute * TLSRoute * UDPRoute" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + namespace: + description: "Namespace is the namespace of the referent. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - namespace + type: object + maxItems: 16 + minItems: 1 + type: array + to: + description: "To describes the resources that may be referenced by the resources described in \"From\". Each entry in this list MUST be considered to be an additional place that references can be valid to, or to put this another way, entries MUST be combined using OR. \n Support: Core" + items: + description: ReferenceGrantTo describes what Kinds are allowed as targets of the references. + properties: + group: + description: "Group is the group of the referent. When empty, the Kubernetes core API group is inferred. \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: "Kind is the kind of the referent. Although implementations may support additional resources, the following types are part of the \"Core\" support level for this field: \n * Secret when used to permit a SecretObjectReference * Service when used to permit a BackendObjectReference" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. When unspecified, this policy refers to all resources of the specified Group and Kind in the local namespace. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - from + - to + type: object + type: object + served: true + storage: true + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_tcproutes.yaml b/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_tcproutes.yaml new file mode 100644 index 000000000..b2284973c --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_tcproutes.yaml @@ -0,0 +1,284 @@ +# +# config/crd/experimental/gateway.networking.k8s.io_tcproutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: tcproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: TCPRoute + listKind: TCPRouteList + plural: tcproutes + singular: tcproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: TCPRoute provides a way to route TCP requests. When combined with a Gateway listener, it can be used to forward connections on the port specified by the listener to a set of backends specified by the TCPRoute. + 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: Spec defines the desired state of TCPRoute. + properties: + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. For Services, that means the Service must either be in the same namespace for a \"producer\" route, or the mesh implementation must support and allow \"consumer\" routes for the referenced Service. ReferenceGrant is not applicable for governing ParentRefs to Services - it is not possible to create a \"producer\" route for a Service in a different namespace from the Route. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) This API may be extended in the future to support additional kinds of parent resources. \n ParentRefs must be _distinct_. This means either that: \n * They select different objects. If this is the case, then parentRef entries are distinct. In terms of fields, this means that the multi-part key defined by `group`, `kind`, `namespace`, and `name` must be unique across all parentRef entries in the Route. * They do not select different objects, but for each optional field used, each ParentRef that selects the same object must set the same set of optional fields to different values. If one ParentRef sets a combination of optional fields, all must set the same combination. \n Some examples: \n * If one ParentRef sets `sectionName`, all ParentRefs referencing the same object must also set `sectionName`. * If one ParentRef sets `port`, all ParentRefs referencing the same object must also set `port`. * If one ParentRef sets `sectionName` and `port`, all ParentRefs referencing the same object must also set `sectionName` and `port`. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable other kinds of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n " + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n This API may be extended in the future to support additional kinds of parent resources. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) + rules: + description: Rules are a list of TCP matchers and actions. + items: + description: TCPRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the underlying implementation MUST actively reject connection attempts to this backend. Connection rejections must respect weight; if an invalid backend is requested to have 80% of connections, then 80% of connections must be rejected instead. \n Support: Core for Kubernetes Service \n Support: Extended for Kubernetes ServiceImport \n Support: Implementation-specific for any other resource \n Support for weight: Extended" + items: + description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n \n When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. \n Implementations supporting appProtocol SHOULD recognize the Kubernetes Standard Application Protocols defined in KEP-3726. \n If a Service appProtocol isn't specified, an implementation MAY infer the backend protocol through its own means. Implementations MAY infer the protocol from the Route type referring to the backend Service. \n If a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason. \n \n Note that when the BackendTLSPolicy object is enabled by the implementation, there are some extra rules about validity to consider here. See the fields where this struct is used for more information about the exact behavior." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + maxItems: 16 + minItems: 1 + type: array + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of TCPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + 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 }" + 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 + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_tlsroutes.yaml b/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_tlsroutes.yaml new file mode 100644 index 000000000..fa097ea7f --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_tlsroutes.yaml @@ -0,0 +1,294 @@ +# +# config/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: tlsroutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: TLSRoute + listKind: TLSRouteList + plural: tlsroutes + singular: tlsroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: "The TLSRoute resource is similar to TCPRoute, but can be configured to match against TLS-specific metadata. This allows more flexibility in matching streams for a given TLS listener. \n If you need to forward traffic to a single target for a TLS listener, you could choose to use a TCPRoute with a TLS listener." + 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: Spec defines the desired state of TLSRoute. + properties: + hostnames: + description: "Hostnames defines a set of SNI names that should match against the SNI attribute of TLS ClientHello message in TLS handshake. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed in SNI names per RFC 6066. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and TLSRoute, there must be at least one intersecting hostname for the TLSRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches TLSRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches TLSRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `test.example.com` and `*.example.com` would both match. On the other hand, `example.com` and `test.example.net` would not match. \n If both the Listener and TLSRoute have specified hostnames, any TLSRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the TLSRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and TLSRoute have specified hostnames, and none match with the criteria above, then the TLSRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. For Services, that means the Service must either be in the same namespace for a \"producer\" route, or the mesh implementation must support and allow \"consumer\" routes for the referenced Service. ReferenceGrant is not applicable for governing ParentRefs to Services - it is not possible to create a \"producer\" route for a Service in a different namespace from the Route. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) This API may be extended in the future to support additional kinds of parent resources. \n ParentRefs must be _distinct_. This means either that: \n * They select different objects. If this is the case, then parentRef entries are distinct. In terms of fields, this means that the multi-part key defined by `group`, `kind`, `namespace`, and `name` must be unique across all parentRef entries in the Route. * They do not select different objects, but for each optional field used, each ParentRef that selects the same object must set the same set of optional fields to different values. If one ParentRef sets a combination of optional fields, all must set the same combination. \n Some examples: \n * If one ParentRef sets `sectionName`, all ParentRefs referencing the same object must also set `sectionName`. * If one ParentRef sets `port`, all ParentRefs referencing the same object must also set `port`. * If one ParentRef sets `sectionName` and `port`, all ParentRefs referencing the same object must also set `sectionName` and `port`. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable other kinds of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n " + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n This API may be extended in the future to support additional kinds of parent resources. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) + rules: + description: Rules are a list of TLS matchers and actions. + items: + description: TLSRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the rule performs no forwarding; if no filters are specified that would result in a response being sent, the underlying implementation must actively reject request attempts to this backend, by rejecting the connection or returning a 500 status code. Request rejections must respect weight; if an invalid backend is requested to have 80% of requests, then 80% of requests must be rejected instead. \n Support: Core for Kubernetes Service \n Support: Extended for Kubernetes ServiceImport \n Support: Implementation-specific for any other resource \n Support for weight: Extended" + items: + description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n \n When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. \n Implementations supporting appProtocol SHOULD recognize the Kubernetes Standard Application Protocols defined in KEP-3726. \n If a Service appProtocol isn't specified, an implementation MAY infer the backend protocol through its own means. Implementations MAY infer the protocol from the Route type referring to the backend Service. \n If a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason. \n \n Note that when the BackendTLSPolicy object is enabled by the implementation, there are some extra rules about validity to consider here. See the fields where this struct is used for more information about the exact behavior." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + maxItems: 16 + minItems: 1 + type: array + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of TLSRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + 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 }" + 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 + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_udproutes.yaml b/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_udproutes.yaml new file mode 100644 index 000000000..bb48563b9 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/gateway.networking.k8s.io_udproutes.yaml @@ -0,0 +1,284 @@ +# +# config/crd/experimental/gateway.networking.k8s.io_udproutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/2466 + gateway.networking.k8s.io/bundle-version: v1.0.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: udproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: UDPRoute + listKind: UDPRouteList + plural: udproutes + singular: udproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: UDPRoute provides a way to route UDP traffic. When combined with a Gateway listener, it can be used to forward traffic on the port specified by the listener to a set of backends specified by the UDPRoute. + 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: Spec defines the desired state of UDPRoute. + properties: + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. For Services, that means the Service must either be in the same namespace for a \"producer\" route, or the mesh implementation must support and allow \"consumer\" routes for the referenced Service. ReferenceGrant is not applicable for governing ParentRefs to Services - it is not possible to create a \"producer\" route for a Service in a different namespace from the Route. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) This API may be extended in the future to support additional kinds of parent resources. \n ParentRefs must be _distinct_. This means either that: \n * They select different objects. If this is the case, then parentRef entries are distinct. In terms of fields, this means that the multi-part key defined by `group`, `kind`, `namespace`, and `name` must be unique across all parentRef entries in the Route. * They do not select different objects, but for each optional field used, each ParentRef that selects the same object must set the same set of optional fields to different values. If one ParentRef sets a combination of optional fields, all must set the same combination. \n Some examples: \n * If one ParentRef sets `sectionName`, all ParentRefs referencing the same object must also set `sectionName`. * If one ParentRef sets `port`, all ParentRefs referencing the same object must also set `port`. * If one ParentRef sets `sectionName` and `port`, all ParentRefs referencing the same object must also set `sectionName` and `port`. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable other kinds of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n " + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n This API may be extended in the future to support additional kinds of parent resources. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) + rules: + description: Rules are a list of UDP matchers and actions. + items: + description: UDPRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the underlying implementation MUST actively reject connection attempts to this backend. Packet drops must respect weight; if an invalid backend is requested to have 80% of the packets, then 80% of packets must be dropped instead. \n Support: Core for Kubernetes Service \n Support: Extended for Kubernetes ServiceImport \n Support: Implementation-specific for any other resource \n Support for weight: Extended" + items: + description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n \n When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. \n Implementations supporting appProtocol SHOULD recognize the Kubernetes Standard Application Protocols defined in KEP-3726. \n If a Service appProtocol isn't specified, an implementation MAY infer the backend protocol through its own means. Implementations MAY infer the protocol from the Route type referring to the backend Service. \n If a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason. \n \n Note that when the BackendTLSPolicy object is enabled by the implementation, there are some extra rules about validity to consider here. See the fields where this struct is used for more information about the exact behavior." + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\". \n Defaults to \"Service\" when not specified. \n ExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services. \n Support: Core (Services with a type other than ExternalName) \n Support: Implementation-specific (Services with type ExternalName)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' + maxItems: 16 + minItems: 1 + type: array + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of UDPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + 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 }" + 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 + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n There are two kinds of parent resources with \"Core\" support: \n * Gateway (Gateway conformance profile) * Service (Mesh conformance profile, experimental, ClusterIP Services only) \n Support for other resources is Implementation-Specific." + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service. \n ParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. Note that attaching Routes to Services as Parents is part of experimental Mesh support and is not supported for any other purpose. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/charts/traefik/traefik/30.0.2/crds/hub.traefik.io_accesscontrolpolicies.yaml b/charts/traefik/traefik/30.0.2/crds/hub.traefik.io_accesscontrolpolicies.yaml new file mode 100644 index 000000000..821f969b6 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/hub.traefik.io_accesscontrolpolicies.yaml @@ -0,0 +1,368 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: accesscontrolpolicies.hub.traefik.io +spec: + group: hub.traefik.io + names: + kind: AccessControlPolicy + listKind: AccessControlPolicyList + plural: accesscontrolpolicies + singular: accesscontrolpolicy + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: AccessControlPolicy defines an access control policy. + 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: AccessControlPolicySpec configures an access control policy. + properties: + apiKey: + description: AccessControlPolicyAPIKey configure an APIKey control + policy. + properties: + forwardHeaders: + additionalProperties: + type: string + description: ForwardHeaders instructs the middleware to forward + key metadata as header values upon successful authentication. + type: object + keySource: + description: KeySource defines how to extract API keys from requests. + properties: + cookie: + description: Cookie is the name of a cookie. + type: string + header: + description: Header is the name of a header. + type: string + headerAuthScheme: + description: |- + HeaderAuthScheme sets an optional auth scheme when Header is set to "Authorization". + If set, this scheme is removed from the token, and all requests not including it are dropped. + type: string + query: + description: Query is the name of a query parameter. + type: string + type: object + keys: + description: Keys define the set of authorized keys to access + a protected resource. + items: + description: AccessControlPolicyAPIKeyKey defines an API key. + properties: + id: + description: ID is the unique identifier of the key. + type: string + metadata: + additionalProperties: + type: string + description: Metadata holds arbitrary metadata for this + key, can be used by ForwardHeaders. + type: object + value: + description: Value is the SHAKE-256 hash (using 64 bytes) + of the API key. + type: string + required: + - id + - value + type: object + type: array + required: + - keySource + type: object + basicAuth: + description: AccessControlPolicyBasicAuth holds the HTTP basic authentication + configuration. + properties: + forwardUsernameHeader: + type: string + realm: + type: string + stripAuthorizationHeader: + type: boolean + users: + items: + type: string + type: array + type: object + jwt: + description: AccessControlPolicyJWT configures a JWT access control + policy. + properties: + claims: + type: string + forwardHeaders: + additionalProperties: + type: string + type: object + jwksFile: + type: string + jwksUrl: + type: string + publicKey: + type: string + signingSecret: + type: string + signingSecretBase64Encoded: + type: boolean + stripAuthorizationHeader: + type: boolean + tokenQueryKey: + type: string + type: object + oAuthIntro: + description: AccessControlOAuthIntro configures an OAuth 2.0 Token + Introspection access control policy. + properties: + claims: + type: string + clientConfig: + description: AccessControlOAuthIntroClientConfig configures the + OAuth 2.0 client for issuing token introspection requests. + properties: + headers: + additionalProperties: + type: string + description: Headers to set when sending requests to the Authorization + Server. + type: object + maxRetries: + default: 3 + description: MaxRetries defines the number of retries for + introspection requests. + type: integer + timeoutSeconds: + default: 5 + description: TimeoutSeconds configures the maximum amount + of seconds to wait before giving up on requests. + type: integer + tls: + description: TLS configures TLS communication with the Authorization + Server. + properties: + ca: + description: CA sets the CA bundle used to sign the Authorization + Server certificate. + type: string + insecureSkipVerify: + description: |- + InsecureSkipVerify skips the Authorization Server certificate validation. + For testing purposes only, do not use in production. + type: boolean + type: object + tokenTypeHint: + description: |- + TokenTypeHint is a hint to pass to the Authorization Server. + See https://tools.ietf.org/html/rfc7662#section-2.1 for more information. + type: string + url: + description: URL of the Authorization Server. + type: string + required: + - url + type: object + forwardHeaders: + additionalProperties: + type: string + type: object + tokenSource: + description: |- + TokenSource describes how to extract tokens from HTTP requests. + If multiple sources are set, the order is the following: header > query > cookie. + properties: + cookie: + description: Cookie is the name of a cookie. + type: string + header: + description: Header is the name of a header. + type: string + headerAuthScheme: + description: |- + HeaderAuthScheme sets an optional auth scheme when Header is set to "Authorization". + If set, this scheme is removed from the token, and all requests not including it are dropped. + type: string + query: + description: Query is the name of a query parameter. + type: string + type: object + required: + - clientConfig + - tokenSource + type: object + oidc: + description: AccessControlPolicyOIDC holds the OIDC authentication + configuration. + properties: + authParams: + additionalProperties: + type: string + type: object + claims: + type: string + clientId: + type: string + disableAuthRedirectionPaths: + items: + type: string + type: array + forwardHeaders: + additionalProperties: + type: string + type: object + issuer: + type: string + logoutUrl: + type: string + redirectUrl: + type: string + scopes: + items: + type: string + type: array + secret: + description: |- + SecretReference represents a Secret Reference. It has enough information to retrieve secret + in any namespace + properties: + name: + description: name is unique within a namespace to reference + a secret resource. + type: string + namespace: + description: namespace defines the space within which the + secret name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + session: + description: Session holds session configuration. + properties: + domain: + type: string + path: + type: string + refresh: + type: boolean + sameSite: + type: string + secure: + type: boolean + type: object + stateCookie: + description: StateCookie holds state cookie configuration. + properties: + domain: + type: string + path: + type: string + sameSite: + type: string + secure: + type: boolean + type: object + type: object + oidcGoogle: + description: AccessControlPolicyOIDCGoogle holds the Google OIDC authentication + configuration. + properties: + authParams: + additionalProperties: + type: string + type: object + clientId: + type: string + emails: + description: Emails are the allowed emails to connect. + items: + type: string + minItems: 1 + type: array + forwardHeaders: + additionalProperties: + type: string + type: object + logoutUrl: + type: string + redirectUrl: + type: string + secret: + description: |- + SecretReference represents a Secret Reference. It has enough information to retrieve secret + in any namespace + properties: + name: + description: name is unique within a namespace to reference + a secret resource. + type: string + namespace: + description: namespace defines the space within which the + secret name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + session: + description: Session holds session configuration. + properties: + domain: + type: string + path: + type: string + refresh: + type: boolean + sameSite: + type: string + secure: + type: boolean + type: object + stateCookie: + description: StateCookie holds state cookie configuration. + properties: + domain: + type: string + path: + type: string + sameSite: + type: string + secure: + type: boolean + type: object + type: object + type: object + status: + description: The current status of this access control policy. + properties: + specHash: + type: string + syncedAt: + format: date-time + type: string + version: + type: string + type: object + type: object + served: true + storage: true diff --git a/charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apiaccesses.yaml b/charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apiaccesses.yaml new file mode 100644 index 000000000..d1b9998c9 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apiaccesses.yaml @@ -0,0 +1,153 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: apiaccesses.hub.traefik.io +spec: + group: hub.traefik.io + names: + kind: APIAccess + listKind: APIAccessList + plural: apiaccesses + singular: apiaccess + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: APIAccess defines who can access to a set of APIs. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: The desired behavior of this APIAccess. + properties: + apiSelector: + description: |- + APISelector selects the APIs that will be accessible to the configured audience. + Multiple APIAccesses can select the same set of APIs. + This field is optional and follows standard label selector semantics. + An empty APISelector matches any API. + 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 + apis: + description: |- + APIs defines a set of APIs that will be accessible to the configured audience. + Multiple APIAccesses can select the same APIs. + When combined with APISelector, this set of APIs is appended to the matching APIs. + items: + description: APIReference references an API. + properties: + name: + description: Name of the API. + maxLength: 253 + type: string + required: + - name + type: object + maxItems: 100 + type: array + x-kubernetes-validations: + - message: duplicated apis + rule: self.all(x, self.exists_one(y, x.name == y.name)) + everyone: + description: Everyone indicates that all users will have access to + the selected APIs. + type: boolean + groups: + description: Groups are the consumer groups that will gain access + to the selected APIs. + items: + type: string + type: array + operationFilter: + description: |- + OperationFilter specifies the allowed operations on APIs and APIVersions. + If not set, all operations are available. + An empty OperationFilter prohibits all operations. + properties: + include: + description: Include defines the names of OperationSets that will + be accessible. + items: + type: string + maxItems: 100 + type: array + type: object + type: object + x-kubernetes-validations: + - message: groups and everyone are mutually exclusive + rule: '(has(self.everyone) && has(self.groups)) ? !(self.everyone && + self.groups.size() > 0) : true' + status: + description: The current status of this APIAccess. + properties: + hash: + description: Hash is a hash representing the APIAccess. + type: string + syncedAt: + format: date-time + type: string + version: + type: string + type: object + type: object + served: true + storage: true diff --git a/charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apiportals.yaml b/charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apiportals.yaml new file mode 100644 index 000000000..bc0417016 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apiportals.yaml @@ -0,0 +1,139 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: apiportals.hub.traefik.io +spec: + group: hub.traefik.io + names: + kind: APIPortal + listKind: APIPortalList + plural: apiportals + singular: apiportal + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: APIPortal defines a developer portal for accessing the documentation + of APIs. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: The desired behavior of this APIPortal. + properties: + description: + description: Description of the APIPortal. + type: string + title: + description: Title is the public facing name of the APIPortal. + type: string + trustedUrls: + description: TrustedURLs are the urls that are trusted by the OAuth + 2.0 authorization server. + items: + type: string + maxItems: 1 + minItems: 1 + type: array + x-kubernetes-validations: + - message: must be a valid URLs + rule: self.all(x, isURL(x)) + ui: + description: UI holds the UI customization options. + properties: + logoUrl: + description: LogoURL is the public URL of the logo. + type: string + type: object + required: + - trustedUrls + type: object + status: + description: The current status of this APIPortal. + properties: + hash: + description: Hash is a hash representing the APIPortal. + type: string + oidc: + description: OIDC is the OIDC configuration for accessing the exposed + APIPortal WebUI. + properties: + clientId: + description: ClientID is the OIDC ClientID for accessing the exposed + APIPortal WebUI. + type: string + companyClaim: + description: CompanyClaim is the name of the JWT claim containing + the user company. + type: string + emailClaim: + description: EmailClaim is the name of the JWT claim containing + the user email. + type: string + firstnameClaim: + description: FirstnameClaim is the name of the JWT claim containing + the user firstname. + type: string + generic: + description: Generic indicates whether or not the APIPortal authentication + relies on Generic OIDC. + type: boolean + groupsClaim: + description: GroupsClaim is the name of the JWT claim containing + the user groups. + type: string + issuer: + description: Issuer is the OIDC issuer for accessing the exposed + APIPortal WebUI. + type: string + lastnameClaim: + description: LastnameClaim is the name of the JWT claim containing + the user lastname. + type: string + scopes: + description: Scopes is the OIDC scopes for getting user attributes + during the authentication to the exposed APIPortal WebUI. + type: string + secretName: + description: SecretName is the name of the secret containing the + OIDC ClientSecret for accessing the exposed APIPortal WebUI. + type: string + syncedAttributes: + description: SyncedAttributes configure the user attributes to + sync. + items: + type: string + type: array + userIdClaim: + description: UserIDClaim is the name of the JWT claim containing + the user ID. + type: string + type: object + syncedAt: + format: date-time + type: string + version: + type: string + type: object + type: object + served: true + storage: true diff --git a/charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apiratelimits.yaml b/charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apiratelimits.yaml new file mode 100644 index 000000000..8e328d3c5 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apiratelimits.yaml @@ -0,0 +1,166 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: apiratelimits.hub.traefik.io +spec: + group: hub.traefik.io + names: + kind: APIRateLimit + listKind: APIRateLimitList + plural: apiratelimits + singular: apiratelimit + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: APIRateLimit defines how group of consumers are rate limited + on a set of APIs. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: The desired behavior of this APIRateLimit. + properties: + apiSelector: + description: |- + APISelector selects the APIs that will be rate limited. + Multiple APIRateLimits can select the same set of APIs. + This field is optional and follows standard label selector semantics. + An empty APISelector matches any API. + 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 + apis: + description: |- + APIs defines a set of APIs that will be rate limited. + Multiple APIRateLimits can select the same APIs. + When combined with APISelector, this set of APIs is appended to the matching APIs. + items: + description: APIReference references an API. + properties: + name: + description: Name of the API. + maxLength: 253 + type: string + required: + - name + type: object + maxItems: 100 + type: array + x-kubernetes-validations: + - message: duplicated apis + rule: self.all(x, self.exists_one(y, x.name == y.name)) + everyone: + description: |- + Everyone indicates that all users will, by default, be rate limited with this configuration. + If an APIRateLimit explicitly target a group, the default rate limit will be ignored. + type: boolean + groups: + description: |- + Groups are the consumer groups that will be rate limited. + Multiple APIRateLimits can target the same set of consumer groups, the most restrictive one applies. + When a consumer belongs to multiple groups, the least restrictive APIRateLimit applies. + items: + type: string + type: array + limit: + description: Limit is the maximum number of token in the bucket. + type: integer + x-kubernetes-validations: + - message: must be a positive number + rule: self >= 0 + period: + description: Period is the unit of time for the Limit. + format: duration + type: string + x-kubernetes-validations: + - message: must be between 1s and 1h + rule: self >= duration('1s') && self <= duration('1h') + strategy: + description: |- + Strategy defines how the bucket state will be synchronized between the different Traefik Hub instances. + It can be, either "local" or "distributed". + enum: + - local + - distributed + type: string + required: + - limit + type: object + x-kubernetes-validations: + - message: groups and everyone are mutually exclusive + rule: '(has(self.everyone) && has(self.groups)) ? !(self.everyone && + self.groups.size() > 0) : true' + status: + description: The current status of this APIRateLimit. + properties: + hash: + description: Hash is a hash representing the APIRateLimit. + type: string + syncedAt: + format: date-time + type: string + version: + type: string + type: object + type: object + served: true + storage: true diff --git a/charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apis.yaml b/charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apis.yaml new file mode 100644 index 000000000..a7b9e5e60 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apis.yaml @@ -0,0 +1,190 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: apis.hub.traefik.io +spec: + group: hub.traefik.io + names: + kind: API + listKind: APIList + plural: apis + singular: api + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + API defines an HTTP interface that is exposed to external clients. It specifies the supported versions + and provides instructions for accessing its documentation. Once instantiated, an API object is associated + with an Ingress, IngressRoute, or HTTPRoute resource, enabling the exposure of the described API to the outside world. + 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: APISpec describes the API. + properties: + openApiSpec: + description: OpenAPISpec defines the API contract as an OpenAPI specification. + properties: + operationSets: + description: OperationSets defines the sets of operations to be + referenced for granular filtering in APIAccesses. + items: + description: |- + OperationSet gives a name to a set of matching OpenAPI operations. + This set of operations can then be referenced for granular filtering in APIAccesses. + properties: + matchers: + description: Matchers defines a list of alternative rules + for matching OpenAPI operations. + items: + description: OperationMatcher defines criteria for matching + an OpenAPI operation. + minProperties: 1 + properties: + methods: + description: Methods specifies the HTTP methods to + be included for selection. + items: + type: string + maxItems: 10 + type: array + path: + description: Path specifies the exact path of the + operations to select. + maxLength: 255 + type: string + x-kubernetes-validations: + - message: must start with a '/' + rule: self.startsWith('/') + - message: cannot contains '../' + rule: '!self.matches(r"""(\/\.\.\/)|(\/\.\.$)""")' + pathPrefix: + description: PathPrefix specifies the path prefix + of the operations to select. + maxLength: 255 + type: string + x-kubernetes-validations: + - message: must start with a '/' + rule: self.startsWith('/') + - message: cannot contains '../' + rule: '!self.matches(r"""(\/\.\.\/)|(\/\.\.$)""")' + pathRegex: + description: PathRegex specifies a regular expression + pattern for matching operations based on their paths. + type: string + type: object + x-kubernetes-validations: + - message: path, pathPrefix and pathRegex are mutually + exclusive + rule: '[has(self.path), has(self.pathPrefix), has(self.pathRegex)].filter(x, + x).size() <= 1' + maxItems: 100 + minItems: 1 + type: array + name: + description: Name is the name of the OperationSet to reference + in APIAccesses. + maxLength: 253 + type: string + required: + - matchers + - name + type: object + maxItems: 100 + type: array + override: + description: Override holds data used to override OpenAPI specification. + properties: + servers: + items: + properties: + url: + type: string + x-kubernetes-validations: + - message: must be a valid URL + rule: isURL(self) + required: + - url + type: object + maxItems: 100 + minItems: 1 + type: array + required: + - servers + type: object + path: + description: |- + Path specifies the endpoint path within the Kubernetes Service where the OpenAPI specification can be obtained. + The Service queried is determined by the associated Ingress, IngressRoute, or HTTPRoute resource to which the API is attached. + It's important to note that this option is incompatible if the Ingress or IngressRoute specifies multiple backend services. + The Path must be accessible via a GET request method and should serve a YAML or JSON document containing the OpenAPI specification. + maxLength: 255 + type: string + x-kubernetes-validations: + - message: must start with a '/' + rule: self.startsWith('/') + - message: cannot contains '../' + rule: '!self.matches(r"""(\/\.\.\/)|(\/\.\.$)""")' + url: + description: |- + URL is a Traefik Hub agent accessible URL for obtaining the OpenAPI specification. + The URL must be accessible via a GET request method and should serve a YAML or JSON document containing the OpenAPI specification. + type: string + x-kubernetes-validations: + - message: must be a valid URL + rule: isURL(self) + type: object + x-kubernetes-validations: + - message: path or url must be defined + rule: has(self.path) || has(self.url) + versions: + description: Versions are the different APIVersions available. + items: + description: APIVersionRef references an APIVersion. + properties: + name: + description: Name of the APIVersion. + maxLength: 253 + type: string + required: + - name + type: object + maxItems: 100 + minItems: 1 + type: array + type: object + status: + description: The current status of this API. + properties: + hash: + description: Hash is a hash representing the API. + type: string + syncedAt: + format: date-time + type: string + version: + type: string + type: object + type: object + served: true + storage: true diff --git a/charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apiversions.yaml b/charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apiversions.yaml new file mode 100644 index 000000000..97184effe --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/hub.traefik.io_apiversions.yaml @@ -0,0 +1,194 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: apiversions.hub.traefik.io +spec: + group: hub.traefik.io + names: + kind: APIVersion + listKind: APIVersionList + plural: apiversions + singular: apiversion + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.title + name: Title + type: string + - jsonPath: .spec.release + name: Release + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: APIVersion defines a version of an 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: The desired behavior of this APIVersion. + properties: + openApiSpec: + description: OpenAPISpec defines the API contract as an OpenAPI specification. + properties: + operationSets: + description: OperationSets defines the sets of operations to be + referenced for granular filtering in APIAccesses. + items: + description: |- + OperationSet gives a name to a set of matching OpenAPI operations. + This set of operations can then be referenced for granular filtering in APIAccesses. + properties: + matchers: + description: Matchers defines a list of alternative rules + for matching OpenAPI operations. + items: + description: OperationMatcher defines criteria for matching + an OpenAPI operation. + minProperties: 1 + properties: + methods: + description: Methods specifies the HTTP methods to + be included for selection. + items: + type: string + maxItems: 10 + type: array + path: + description: Path specifies the exact path of the + operations to select. + maxLength: 255 + type: string + x-kubernetes-validations: + - message: must start with a '/' + rule: self.startsWith('/') + - message: cannot contains '../' + rule: '!self.matches(r"""(\/\.\.\/)|(\/\.\.$)""")' + pathPrefix: + description: PathPrefix specifies the path prefix + of the operations to select. + maxLength: 255 + type: string + x-kubernetes-validations: + - message: must start with a '/' + rule: self.startsWith('/') + - message: cannot contains '../' + rule: '!self.matches(r"""(\/\.\.\/)|(\/\.\.$)""")' + pathRegex: + description: PathRegex specifies a regular expression + pattern for matching operations based on their paths. + type: string + type: object + x-kubernetes-validations: + - message: path, pathPrefix and pathRegex are mutually + exclusive + rule: '[has(self.path), has(self.pathPrefix), has(self.pathRegex)].filter(x, + x).size() <= 1' + maxItems: 100 + minItems: 1 + type: array + name: + description: Name is the name of the OperationSet to reference + in APIAccesses. + maxLength: 253 + type: string + required: + - matchers + - name + type: object + maxItems: 100 + type: array + override: + description: Override holds data used to override OpenAPI specification. + properties: + servers: + items: + properties: + url: + type: string + x-kubernetes-validations: + - message: must be a valid URL + rule: isURL(self) + required: + - url + type: object + maxItems: 100 + minItems: 1 + type: array + required: + - servers + type: object + path: + description: |- + Path specifies the endpoint path within the Kubernetes Service where the OpenAPI specification can be obtained. + The Service queried is determined by the associated Ingress, IngressRoute, or HTTPRoute resource to which the API is attached. + It's important to note that this option is incompatible if the Ingress or IngressRoute specifies multiple backend services. + The Path must be accessible via a GET request method and should serve a YAML or JSON document containing the OpenAPI specification. + maxLength: 255 + type: string + x-kubernetes-validations: + - message: must start with a '/' + rule: self.startsWith('/') + - message: cannot contains '../' + rule: '!self.matches(r"""(\/\.\.\/)|(\/\.\.$)""")' + url: + description: |- + URL is a Traefik Hub agent accessible URL for obtaining the OpenAPI specification. + The URL must be accessible via a GET request method and should serve a YAML or JSON document containing the OpenAPI specification. + type: string + x-kubernetes-validations: + - message: must be a valid URL + rule: isURL(self) + type: object + x-kubernetes-validations: + - message: path or url must be defined + rule: has(self.path) || has(self.url) + release: + description: |- + Release is the version number of the API. + This value must follow the SemVer format: https://semver.org/ + maxLength: 100 + type: string + x-kubernetes-validations: + - message: must be a valid semver version + rule: self.matches(r"""^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$""") + title: + description: Title is the public facing name of the APIVersion. + type: string + required: + - release + type: object + status: + description: The current status of this APIVersion. + properties: + hash: + description: Hash is a hash representing the APIVersion. + type: string + syncedAt: + format: date-time + type: string + version: + type: string + type: object + type: object + served: true + storage: true + subresources: {} diff --git a/charts/traefik/traefik/30.0.2/crds/traefik.io_ingressroutes.yaml b/charts/traefik/traefik/30.0.2/crds/traefik.io_ingressroutes.yaml new file mode 100644 index 000000000..7b23dba43 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/traefik.io_ingressroutes.yaml @@ -0,0 +1,366 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: ingressroutes.traefik.io +spec: + group: traefik.io + names: + kind: IngressRoute + listKind: IngressRouteList + plural: ingressroutes + singular: ingressroute + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: IngressRoute is the CRD implementation of a Traefik HTTP Router. + 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: IngressRouteSpec defines the desired state of IngressRoute. + properties: + entryPoints: + description: |- + EntryPoints defines the list of entry point names to bind to. + Entry points have to be configured in the static configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ + Default: all. + items: + type: string + type: array + routes: + description: Routes defines the list of routes. + items: + description: Route holds the HTTP route configuration. + properties: + kind: + description: |- + Kind defines the kind of the route. + Rule is the only supported kind. + enum: + - Rule + type: string + match: + description: |- + Match defines the router's rule. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule + type: string + middlewares: + description: |- + Middlewares defines the list of references to Middleware resources. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-middleware + items: + description: MiddlewareRef is a reference to a Middleware + resource. + properties: + name: + description: Name defines the name of the referenced Middleware + resource. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Middleware resource. + type: string + required: + - name + type: object + type: array + priority: + description: |- + Priority defines the router's priority. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority + type: integer + services: + description: |- + Services defines the list of Service. + It can contain any combination of TraefikService and/or reference to a Kubernetes Service. + items: + description: Service defines an upstream HTTP service to proxy + traffic to. + properties: + healthCheck: + description: Healthcheck defines health checks for ExternalName + services. + properties: + followRedirects: + description: |- + FollowRedirects defines whether redirects should be followed during the health check calls. + Default: true + type: boolean + headers: + additionalProperties: + type: string + description: Headers defines custom headers to be + sent to the health check endpoint. + type: object + hostname: + description: Hostname defines the value of hostname + in the Host header of the health check request. + type: string + interval: + anyOf: + - type: integer + - type: string + description: |- + Interval defines the frequency of the health check calls. + Default: 30s + x-kubernetes-int-or-string: true + method: + description: Method defines the healthcheck method. + type: string + mode: + description: |- + Mode defines the health check mode. + If defined to grpc, will use the gRPC health check protocol to probe the server. + Default: http + type: string + path: + description: Path defines the server URL path for + the health check endpoint. + type: string + port: + description: Port defines the server URL port for + the health check endpoint. + type: integer + scheme: + description: Scheme replaces the server URL scheme + for the health check endpoint. + type: string + status: + description: Status defines the expected HTTP status + code of the response to the health check request. + type: integer + timeout: + anyOf: + - type: integer + - type: string + description: |- + Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. + Default: 5s + x-kubernetes-int-or-string: true + type: object + kind: + description: Kind defines the kind of the Service. + enum: + - Service + - TraefikService + type: string + name: + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Kubernetes Service or TraefikService. + type: string + nativeLB: + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. + type: boolean + nodePortLB: + description: |- + NodePortLB controls, when creating the load-balancer, + whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort. + It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes. + By default, NodePortLB is false. + type: boolean + passHostHeader: + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. + By default, passHostHeader is true. + type: boolean + port: + anyOf: + - type: integer + - type: string + description: |- + Port defines the port of a Kubernetes Service. + This can be a reference to a named port. + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding defines how Traefik forwards + the response from the upstream Kubernetes Service to + the client. + properties: + flushInterval: + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms + type: string + type: object + scheme: + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. + type: string + serversTransport: + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. + type: string + sticky: + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions + properties: + cookie: + description: Cookie defines the sticky cookie configuration. + properties: + httpOnly: + description: HTTPOnly defines whether the cookie + can be accessed by client-side APIs, such as + JavaScript. + type: boolean + maxAge: + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. + type: integer + name: + description: Name defines the Cookie name. + type: string + sameSite: + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite + type: string + secure: + description: Secure defines whether the cookie + can only be transmitted over an encrypted connection + (i.e. HTTPS). + type: boolean + type: object + type: object + strategy: + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. + type: string + weight: + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round Robin). + type: integer + required: + - name + type: object + type: array + syntax: + description: |- + Syntax defines the router's rule syntax. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rulesyntax + type: string + required: + - kind + - match + type: object + type: array + tls: + description: |- + TLS defines the TLS configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls + properties: + certResolver: + description: |- + CertResolver defines the name of the certificate resolver to use. + Cert resolvers have to be configured in the static configuration. + More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers + type: string + domains: + description: |- + Domains defines the list of domains that will be used to issue certificates. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains + items: + description: Domain holds a domain name with SANs. + properties: + main: + description: Main defines the main domain name. + type: string + sans: + description: SANs defines the subject alternative domain + names. + items: + type: string + type: array + type: object + type: array + options: + description: |- + Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. + If not defined, the `default` TLSOption is used. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options + properties: + name: + description: |- + Name defines the name of the referenced TLSOption. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption + type: string + namespace: + description: |- + Namespace defines the namespace of the referenced TLSOption. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption + type: string + required: + - name + type: object + secretName: + description: SecretName is the name of the referenced Kubernetes + Secret to specify the certificate details. + type: string + store: + description: |- + Store defines the reference to the TLSStore, that will be used to store certificates. + Please note that only `default` TLSStore can be used. + properties: + name: + description: |- + Name defines the name of the referenced TLSStore. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore + type: string + namespace: + description: |- + Namespace defines the namespace of the referenced TLSStore. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore + type: string + required: + - name + type: object + type: object + required: + - routes + type: object + required: + - metadata + - spec + type: object + served: true + storage: true diff --git a/charts/traefik/traefik/30.0.2/crds/traefik.io_ingressroutetcps.yaml b/charts/traefik/traefik/30.0.2/crds/traefik.io_ingressroutetcps.yaml new file mode 100644 index 000000000..f3eea5e74 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/traefik.io_ingressroutetcps.yaml @@ -0,0 +1,247 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: ingressroutetcps.traefik.io +spec: + group: traefik.io + names: + kind: IngressRouteTCP + listKind: IngressRouteTCPList + plural: ingressroutetcps + singular: ingressroutetcp + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: IngressRouteTCP is the CRD implementation of a Traefik TCP Router. + 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: IngressRouteTCPSpec defines the desired state of IngressRouteTCP. + properties: + entryPoints: + description: |- + EntryPoints defines the list of entry point names to bind to. + Entry points have to be configured in the static configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ + Default: all. + items: + type: string + type: array + routes: + description: Routes defines the list of routes. + items: + description: RouteTCP holds the TCP route configuration. + properties: + match: + description: |- + Match defines the router's rule. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule_1 + type: string + middlewares: + description: Middlewares defines the list of references to MiddlewareTCP + resources. + items: + description: ObjectReference is a generic reference to a Traefik + resource. + properties: + name: + description: Name defines the name of the referenced Traefik + resource. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Traefik resource. + type: string + required: + - name + type: object + type: array + priority: + description: |- + Priority defines the router's priority. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority_1 + type: integer + services: + description: Services defines the list of TCP services. + items: + description: ServiceTCP defines an upstream TCP service to + proxy traffic to. + properties: + name: + description: Name defines the name of the referenced Kubernetes + Service. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Kubernetes Service. + type: string + nativeLB: + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. + type: boolean + nodePortLB: + description: |- + NodePortLB controls, when creating the load-balancer, + whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort. + It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes. + By default, NodePortLB is false. + type: boolean + port: + anyOf: + - type: integer + - type: string + description: |- + Port defines the port of a Kubernetes Service. + This can be a reference to a named port. + x-kubernetes-int-or-string: true + proxyProtocol: + description: |- + ProxyProtocol defines the PROXY protocol configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#proxy-protocol + properties: + version: + description: Version defines the PROXY Protocol version + to use. + type: integer + type: object + serversTransport: + description: |- + ServersTransport defines the name of ServersTransportTCP resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. + type: string + terminationDelay: + description: |- + TerminationDelay defines the deadline that the proxy sets, after one of its connected peers indicates + it has closed the writing capability of its connection, to close the reading capability as well, + hence fully terminating the connection. + It is a duration in milliseconds, defaulting to 100. + A negative value means an infinite deadline (i.e. the reading capability is never closed). + Deprecated: TerminationDelay is not supported APIVersion traefik.io/v1, please use ServersTransport to configure the TerminationDelay instead. + type: integer + tls: + description: TLS determines whether to use TLS when dialing + with the backend. + type: boolean + weight: + description: Weight defines the weight used when balancing + requests between multiple Kubernetes Service. + type: integer + required: + - name + - port + type: object + type: array + syntax: + description: |- + Syntax defines the router's rule syntax. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rulesyntax_1 + type: string + required: + - match + type: object + type: array + tls: + description: |- + TLS defines the TLS configuration on a layer 4 / TCP Route. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls_1 + properties: + certResolver: + description: |- + CertResolver defines the name of the certificate resolver to use. + Cert resolvers have to be configured in the static configuration. + More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers + type: string + domains: + description: |- + Domains defines the list of domains that will be used to issue certificates. + More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains + items: + description: Domain holds a domain name with SANs. + properties: + main: + description: Main defines the main domain name. + type: string + sans: + description: SANs defines the subject alternative domain + names. + items: + type: string + type: array + type: object + type: array + options: + description: |- + Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. + If not defined, the `default` TLSOption is used. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options + properties: + name: + description: Name defines the name of the referenced Traefik + resource. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Traefik resource. + type: string + required: + - name + type: object + passthrough: + description: Passthrough defines whether a TLS router will terminate + the TLS connection. + type: boolean + secretName: + description: SecretName is the name of the referenced Kubernetes + Secret to specify the certificate details. + type: string + store: + description: |- + Store defines the reference to the TLSStore, that will be used to store certificates. + Please note that only `default` TLSStore can be used. + properties: + name: + description: Name defines the name of the referenced Traefik + resource. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Traefik resource. + type: string + required: + - name + type: object + type: object + required: + - routes + type: object + required: + - metadata + - spec + type: object + served: true + storage: true diff --git a/charts/traefik/traefik/30.0.2/crds/traefik.io_ingressrouteudps.yaml b/charts/traefik/traefik/30.0.2/crds/traefik.io_ingressrouteudps.yaml new file mode 100644 index 000000000..19bbfe62e --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/traefik.io_ingressrouteudps.yaml @@ -0,0 +1,111 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: ingressrouteudps.traefik.io +spec: + group: traefik.io + names: + kind: IngressRouteUDP + listKind: IngressRouteUDPList + plural: ingressrouteudps + singular: ingressrouteudp + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: IngressRouteUDP is a CRD implementation of a Traefik UDP Router. + 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: IngressRouteUDPSpec defines the desired state of a IngressRouteUDP. + properties: + entryPoints: + description: |- + EntryPoints defines the list of entry point names to bind to. + Entry points have to be configured in the static configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ + Default: all. + items: + type: string + type: array + routes: + description: Routes defines the list of routes. + items: + description: RouteUDP holds the UDP route configuration. + properties: + services: + description: Services defines the list of UDP services. + items: + description: ServiceUDP defines an upstream UDP service to + proxy traffic to. + properties: + name: + description: Name defines the name of the referenced Kubernetes + Service. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Kubernetes Service. + type: string + nativeLB: + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. + type: boolean + nodePortLB: + description: |- + NodePortLB controls, when creating the load-balancer, + whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort. + It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes. + By default, NodePortLB is false. + type: boolean + port: + anyOf: + - type: integer + - type: string + description: |- + Port defines the port of a Kubernetes Service. + This can be a reference to a named port. + x-kubernetes-int-or-string: true + weight: + description: Weight defines the weight used when balancing + requests between multiple Kubernetes Service. + type: integer + required: + - name + - port + type: object + type: array + type: object + type: array + required: + - routes + type: object + required: + - metadata + - spec + type: object + served: true + storage: true diff --git a/charts/traefik/traefik/30.0.2/crds/traefik.io_middlewares.yaml b/charts/traefik/traefik/30.0.2/crds/traefik.io_middlewares.yaml new file mode 100644 index 000000000..0d005e64d --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/traefik.io_middlewares.yaml @@ -0,0 +1,1098 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: middlewares.traefik.io +spec: + group: traefik.io + names: + kind: Middleware + listKind: MiddlewareList + plural: middlewares + singular: middleware + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + Middleware is the CRD implementation of a Traefik Middleware. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/overview/ + 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: MiddlewareSpec defines the desired state of a Middleware. + properties: + addPrefix: + description: |- + AddPrefix holds the add prefix middleware configuration. + This middleware updates the path of a request before forwarding it. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/addprefix/ + properties: + prefix: + description: |- + Prefix is the string to add before the current path in the requested URL. + It should include a leading slash (/). + type: string + type: object + basicAuth: + description: |- + BasicAuth holds the basic auth middleware configuration. + This middleware restricts access to your services to known users. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/ + properties: + headerField: + description: |- + HeaderField defines a header field to store the authenticated user. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield + type: string + realm: + description: |- + Realm allows the protected resources on a server to be partitioned into a set of protection spaces, each with its own authentication scheme. + Default: traefik. + type: string + removeHeader: + description: |- + RemoveHeader sets the removeHeader option to true to remove the authorization header before forwarding the request to your service. + Default: false. + type: boolean + secret: + description: Secret is the name of the referenced Kubernetes Secret + containing user credentials. + type: string + type: object + buffering: + description: |- + Buffering holds the buffering middleware configuration. + This middleware retries or limits the size of requests that can be forwarded to backends. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#maxrequestbodybytes + properties: + maxRequestBodyBytes: + description: |- + MaxRequestBodyBytes defines the maximum allowed body size for the request (in bytes). + If the request exceeds the allowed size, it is not forwarded to the service, and the client gets a 413 (Request Entity Too Large) response. + Default: 0 (no maximum). + format: int64 + type: integer + maxResponseBodyBytes: + description: |- + MaxResponseBodyBytes defines the maximum allowed response size from the service (in bytes). + If the response exceeds the allowed size, it is not forwarded to the client. The client gets a 500 (Internal Server Error) response instead. + Default: 0 (no maximum). + format: int64 + type: integer + memRequestBodyBytes: + description: |- + MemRequestBodyBytes defines the threshold (in bytes) from which the request will be buffered on disk instead of in memory. + Default: 1048576 (1Mi). + format: int64 + type: integer + memResponseBodyBytes: + description: |- + MemResponseBodyBytes defines the threshold (in bytes) from which the response will be buffered on disk instead of in memory. + Default: 1048576 (1Mi). + format: int64 + type: integer + retryExpression: + description: |- + RetryExpression defines the retry conditions. + It is a logical combination of functions with operators AND (&&) and OR (||). + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#retryexpression + type: string + type: object + chain: + description: |- + Chain holds the configuration of the chain middleware. + This middleware enables to define reusable combinations of other pieces of middleware. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/chain/ + properties: + middlewares: + description: Middlewares is the list of MiddlewareRef which composes + the chain. + items: + description: MiddlewareRef is a reference to a Middleware resource. + properties: + name: + description: Name defines the name of the referenced Middleware + resource. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Middleware resource. + type: string + required: + - name + type: object + type: array + type: object + circuitBreaker: + description: CircuitBreaker holds the circuit breaker configuration. + properties: + checkPeriod: + anyOf: + - type: integer + - type: string + description: CheckPeriod is the interval between successive checks + of the circuit breaker condition (when in standby state). + x-kubernetes-int-or-string: true + expression: + description: Expression is the condition that triggers the tripped + state. + type: string + fallbackDuration: + anyOf: + - type: integer + - type: string + description: FallbackDuration is the duration for which the circuit + breaker will wait before trying to recover (from a tripped state). + x-kubernetes-int-or-string: true + recoveryDuration: + anyOf: + - type: integer + - type: string + description: RecoveryDuration is the duration for which the circuit + breaker will try to recover (as soon as it is in recovering + state). + x-kubernetes-int-or-string: true + responseCode: + description: ResponseCode is the status code that the circuit + breaker will return while it is in the open state. + type: integer + type: object + compress: + description: |- + Compress holds the compress middleware configuration. + This middleware compresses responses before sending them to the client, using gzip compression. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/compress/ + properties: + defaultEncoding: + description: DefaultEncoding specifies the default encoding if + the `Accept-Encoding` header is not in the request or contains + a wildcard (`*`). + type: string + excludedContentTypes: + description: |- + ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing. + `application/grpc` is always excluded. + items: + type: string + type: array + includedContentTypes: + description: IncludedContentTypes defines the list of content + types to compare the Content-Type header of the responses before + compressing. + items: + type: string + type: array + minResponseBodyBytes: + description: |- + MinResponseBodyBytes defines the minimum amount of bytes a response body must have to be compressed. + Default: 1024. + type: integer + type: object + contentType: + description: |- + ContentType holds the content-type middleware configuration. + This middleware exists to enable the correct behavior until at least the default one can be changed in a future version. + properties: + autoDetect: + description: |- + AutoDetect specifies whether to let the `Content-Type` header, if it has not been set by the backend, + be automatically set to a value derived from the contents of the response. + Deprecated: AutoDetect option is deprecated, Content-Type middleware is only meant to be used to enable the content-type detection, please remove any usage of this option. + type: boolean + type: object + digestAuth: + description: |- + DigestAuth holds the digest auth middleware configuration. + This middleware restricts access to your services to known users. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/digestauth/ + properties: + headerField: + description: |- + HeaderField defines a header field to store the authenticated user. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield + type: string + realm: + description: |- + Realm allows the protected resources on a server to be partitioned into a set of protection spaces, each with its own authentication scheme. + Default: traefik. + type: string + removeHeader: + description: RemoveHeader defines whether to remove the authorization + header before forwarding the request to the backend. + type: boolean + secret: + description: Secret is the name of the referenced Kubernetes Secret + containing user credentials. + type: string + type: object + errors: + description: |- + ErrorPage holds the custom error middleware configuration. + This middleware returns a custom page in lieu of the default, according to configured ranges of HTTP Status codes. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/ + properties: + query: + description: |- + Query defines the URL for the error page (hosted by service). + The {status} variable can be used in order to insert the status code in the URL. + type: string + service: + description: |- + Service defines the reference to a Kubernetes Service that will serve the error page. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service + properties: + healthCheck: + description: Healthcheck defines health checks for ExternalName + services. + properties: + followRedirects: + description: |- + FollowRedirects defines whether redirects should be followed during the health check calls. + Default: true + type: boolean + headers: + additionalProperties: + type: string + description: Headers defines custom headers to be sent + to the health check endpoint. + type: object + hostname: + description: Hostname defines the value of hostname in + the Host header of the health check request. + type: string + interval: + anyOf: + - type: integer + - type: string + description: |- + Interval defines the frequency of the health check calls. + Default: 30s + x-kubernetes-int-or-string: true + method: + description: Method defines the healthcheck method. + type: string + mode: + description: |- + Mode defines the health check mode. + If defined to grpc, will use the gRPC health check protocol to probe the server. + Default: http + type: string + path: + description: Path defines the server URL path for the + health check endpoint. + type: string + port: + description: Port defines the server URL port for the + health check endpoint. + type: integer + scheme: + description: Scheme replaces the server URL scheme for + the health check endpoint. + type: string + status: + description: Status defines the expected HTTP status code + of the response to the health check request. + type: integer + timeout: + anyOf: + - type: integer + - type: string + description: |- + Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. + Default: 5s + x-kubernetes-int-or-string: true + type: object + kind: + description: Kind defines the kind of the Service. + enum: + - Service + - TraefikService + type: string + name: + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Kubernetes Service or TraefikService. + type: string + nativeLB: + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. + type: boolean + nodePortLB: + description: |- + NodePortLB controls, when creating the load-balancer, + whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort. + It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes. + By default, NodePortLB is false. + type: boolean + passHostHeader: + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. + By default, passHostHeader is true. + type: boolean + port: + anyOf: + - type: integer + - type: string + description: |- + Port defines the port of a Kubernetes Service. + This can be a reference to a named port. + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding defines how Traefik forwards + the response from the upstream Kubernetes Service to the + client. + properties: + flushInterval: + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms + type: string + type: object + scheme: + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. + type: string + serversTransport: + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. + type: string + sticky: + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions + properties: + cookie: + description: Cookie defines the sticky cookie configuration. + properties: + httpOnly: + description: HTTPOnly defines whether the cookie can + be accessed by client-side APIs, such as JavaScript. + type: boolean + maxAge: + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. + type: integer + name: + description: Name defines the Cookie name. + type: string + sameSite: + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite + type: string + secure: + description: Secure defines whether the cookie can + only be transmitted over an encrypted connection + (i.e. HTTPS). + type: boolean + type: object + type: object + strategy: + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. + type: string + weight: + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round Robin). + type: integer + required: + - name + type: object + status: + description: |- + Status defines which status or range of statuses should result in an error page. + It can be either a status code as a number (500), + as multiple comma-separated numbers (500,502), + as ranges by separating two codes with a dash (500-599), + or a combination of the two (404,418,500-599). + items: + type: string + type: array + type: object + forwardAuth: + description: |- + ForwardAuth holds the forward auth middleware configuration. + This middleware delegates the request authentication to a Service. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/ + properties: + addAuthCookiesToResponse: + description: AddAuthCookiesToResponse defines the list of cookies + to copy from the authentication server response to the response. + items: + type: string + type: array + address: + description: Address defines the authentication server address. + type: string + authRequestHeaders: + description: |- + AuthRequestHeaders defines the list of the headers to copy from the request to the authentication server. + If not set or empty then all request headers are passed. + items: + type: string + type: array + authResponseHeaders: + description: AuthResponseHeaders defines the list of headers to + copy from the authentication server response and set on forwarded + request, replacing any existing conflicting headers. + items: + type: string + type: array + authResponseHeadersRegex: + description: |- + AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/#authresponseheadersregex + type: string + tls: + description: TLS defines the configuration used to secure the + connection to the authentication server. + properties: + caOptional: + description: 'Deprecated: TLS client authentication is a server + side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634).' + type: boolean + caSecret: + description: |- + CASecret is the name of the referenced Kubernetes Secret containing the CA to validate the server certificate. + The CA certificate is extracted from key `tls.ca` or `ca.crt`. + type: string + certSecret: + description: |- + CertSecret is the name of the referenced Kubernetes Secret containing the client certificate. + The client certificate is extracted from the keys `tls.crt` and `tls.key`. + type: string + insecureSkipVerify: + description: InsecureSkipVerify defines whether the server + certificates should be validated. + type: boolean + type: object + trustForwardHeader: + description: 'TrustForwardHeader defines whether to trust (ie: + forward) all X-Forwarded-* headers.' + type: boolean + type: object + grpcWeb: + description: |- + GrpcWeb holds the gRPC web middleware configuration. + This middleware converts a gRPC web request to an HTTP/2 gRPC request. + properties: + allowOrigins: + description: |- + AllowOrigins is a list of allowable origins. + Can also be a wildcard origin "*". + items: + type: string + type: array + type: object + headers: + description: |- + Headers holds the headers middleware configuration. + This middleware manages the requests and responses headers. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/headers/#customrequestheaders + properties: + accessControlAllowCredentials: + description: AccessControlAllowCredentials defines whether the + request can include user credentials. + type: boolean + accessControlAllowHeaders: + description: AccessControlAllowHeaders defines the Access-Control-Request-Headers + values sent in preflight response. + items: + type: string + type: array + accessControlAllowMethods: + description: AccessControlAllowMethods defines the Access-Control-Request-Method + values sent in preflight response. + items: + type: string + type: array + accessControlAllowOriginList: + description: AccessControlAllowOriginList is a list of allowable + origins. Can also be a wildcard origin "*". + items: + type: string + type: array + accessControlAllowOriginListRegex: + description: AccessControlAllowOriginListRegex is a list of allowable + origins written following the Regular Expression syntax (https://golang.org/pkg/regexp/). + items: + type: string + type: array + accessControlExposeHeaders: + description: AccessControlExposeHeaders defines the Access-Control-Expose-Headers + values sent in preflight response. + items: + type: string + type: array + accessControlMaxAge: + description: AccessControlMaxAge defines the time that a preflight + request may be cached. + format: int64 + type: integer + addVaryHeader: + description: AddVaryHeader defines whether the Vary header is + automatically added/updated when the AccessControlAllowOriginList + is set. + type: boolean + allowedHosts: + description: AllowedHosts defines the fully qualified list of + allowed domain names. + items: + type: string + type: array + browserXssFilter: + description: BrowserXSSFilter defines whether to add the X-XSS-Protection + header with the value 1; mode=block. + type: boolean + contentSecurityPolicy: + description: ContentSecurityPolicy defines the Content-Security-Policy + header value. + type: string + contentSecurityPolicyReportOnly: + description: ContentSecurityPolicyReportOnly defines the Content-Security-Policy-Report-Only + header value. + type: string + contentTypeNosniff: + description: ContentTypeNosniff defines whether to add the X-Content-Type-Options + header with the nosniff value. + type: boolean + customBrowserXSSValue: + description: |- + CustomBrowserXSSValue defines the X-XSS-Protection header value. + This overrides the BrowserXssFilter option. + type: string + customFrameOptionsValue: + description: |- + CustomFrameOptionsValue defines the X-Frame-Options header value. + This overrides the FrameDeny option. + type: string + customRequestHeaders: + additionalProperties: + type: string + description: CustomRequestHeaders defines the header names and + values to apply to the request. + type: object + customResponseHeaders: + additionalProperties: + type: string + description: CustomResponseHeaders defines the header names and + values to apply to the response. + type: object + featurePolicy: + description: 'Deprecated: FeaturePolicy option is deprecated, + please use PermissionsPolicy instead.' + type: string + forceSTSHeader: + description: ForceSTSHeader defines whether to add the STS header + even when the connection is HTTP. + type: boolean + frameDeny: + description: FrameDeny defines whether to add the X-Frame-Options + header with the DENY value. + type: boolean + hostsProxyHeaders: + description: HostsProxyHeaders defines the header keys that may + hold a proxied hostname value for the request. + items: + type: string + type: array + isDevelopment: + description: |- + IsDevelopment defines whether to mitigate the unwanted effects of the AllowedHosts, SSL, and STS options when developing. + Usually testing takes place using HTTP, not HTTPS, and on localhost, not your production domain. + If you would like your development environment to mimic production with complete Host blocking, SSL redirects, + and STS headers, leave this as false. + type: boolean + permissionsPolicy: + description: |- + PermissionsPolicy defines the Permissions-Policy header value. + This allows sites to control browser features. + type: string + publicKey: + description: PublicKey is the public key that implements HPKP + to prevent MITM attacks with forged certificates. + type: string + referrerPolicy: + description: |- + ReferrerPolicy defines the Referrer-Policy header value. + This allows sites to control whether browsers forward the Referer header to other sites. + type: string + sslForceHost: + description: 'Deprecated: SSLForceHost option is deprecated, please + use RedirectRegex instead.' + type: boolean + sslHost: + description: 'Deprecated: SSLHost option is deprecated, please + use RedirectRegex instead.' + type: string + sslProxyHeaders: + additionalProperties: + type: string + description: |- + SSLProxyHeaders defines the header keys with associated values that would indicate a valid HTTPS request. + It can be useful when using other proxies (example: "X-Forwarded-Proto": "https"). + type: object + sslRedirect: + description: 'Deprecated: SSLRedirect option is deprecated, please + use EntryPoint redirection or RedirectScheme instead.' + type: boolean + sslTemporaryRedirect: + description: 'Deprecated: SSLTemporaryRedirect option is deprecated, + please use EntryPoint redirection or RedirectScheme instead.' + type: boolean + stsIncludeSubdomains: + description: STSIncludeSubdomains defines whether the includeSubDomains + directive is appended to the Strict-Transport-Security header. + type: boolean + stsPreload: + description: STSPreload defines whether the preload flag is appended + to the Strict-Transport-Security header. + type: boolean + stsSeconds: + description: |- + STSSeconds defines the max-age of the Strict-Transport-Security header. + If set to 0, the header is not set. + format: int64 + type: integer + type: object + inFlightReq: + description: |- + InFlightReq holds the in-flight request middleware configuration. + This middleware limits the number of requests being processed and served concurrently. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/ + properties: + amount: + description: |- + Amount defines the maximum amount of allowed simultaneous in-flight request. + The middleware responds with HTTP 429 Too Many Requests if there are already amount requests in progress (based on the same sourceCriterion strategy). + format: int64 + type: integer + sourceCriterion: + description: |- + SourceCriterion defines what criterion is used to group requests as originating from a common source. + If several strategies are defined at the same time, an error will be raised. + If none are set, the default is to use the requestHost. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/#sourcecriterion + properties: + ipStrategy: + description: |- + IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy + properties: + depth: + description: Depth tells Traefik to use the X-Forwarded-For + header and take the IP located at the depth position + (starting from the right). + type: integer + excludedIPs: + description: ExcludedIPs configures Traefik to scan the + X-Forwarded-For header and select the first IP not in + the list. + items: + type: string + type: array + type: object + requestHeaderName: + description: RequestHeaderName defines the name of the header + used to group incoming requests. + type: string + requestHost: + description: RequestHost defines whether to consider the request + Host as the source. + type: boolean + type: object + type: object + ipAllowList: + description: |- + IPAllowList holds the IP allowlist middleware configuration. + This middleware limits allowed requests based on the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/ + properties: + ipStrategy: + description: |- + IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy + properties: + depth: + description: Depth tells Traefik to use the X-Forwarded-For + header and take the IP located at the depth position (starting + from the right). + type: integer + excludedIPs: + description: ExcludedIPs configures Traefik to scan the X-Forwarded-For + header and select the first IP not in the list. + items: + type: string + type: array + type: object + rejectStatusCode: + description: |- + RejectStatusCode defines the HTTP status code used for refused requests. + If not set, the default is 403 (Forbidden). + type: integer + sourceRange: + description: SourceRange defines the set of allowed IPs (or ranges + of allowed IPs by using CIDR notation). + items: + type: string + type: array + type: object + ipWhiteList: + description: 'Deprecated: please use IPAllowList instead.' + properties: + ipStrategy: + description: |- + IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy + properties: + depth: + description: Depth tells Traefik to use the X-Forwarded-For + header and take the IP located at the depth position (starting + from the right). + type: integer + excludedIPs: + description: ExcludedIPs configures Traefik to scan the X-Forwarded-For + header and select the first IP not in the list. + items: + type: string + type: array + type: object + sourceRange: + description: SourceRange defines the set of allowed IPs (or ranges + of allowed IPs by using CIDR notation). Required. + items: + type: string + type: array + type: object + passTLSClientCert: + description: |- + PassTLSClientCert holds the pass TLS client cert middleware configuration. + This middleware adds the selected data from the passed client TLS certificate to a header. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/passtlsclientcert/ + properties: + info: + description: Info selects the specific client certificate details + you want to add to the X-Forwarded-Tls-Client-Cert-Info header. + properties: + issuer: + description: Issuer defines the client certificate issuer + details to add to the X-Forwarded-Tls-Client-Cert-Info header. + properties: + commonName: + description: CommonName defines whether to add the organizationalUnit + information into the issuer. + type: boolean + country: + description: Country defines whether to add the country + information into the issuer. + type: boolean + domainComponent: + description: DomainComponent defines whether to add the + domainComponent information into the issuer. + type: boolean + locality: + description: Locality defines whether to add the locality + information into the issuer. + type: boolean + organization: + description: Organization defines whether to add the organization + information into the issuer. + type: boolean + province: + description: Province defines whether to add the province + information into the issuer. + type: boolean + serialNumber: + description: SerialNumber defines whether to add the serialNumber + information into the issuer. + type: boolean + type: object + notAfter: + description: NotAfter defines whether to add the Not After + information from the Validity part. + type: boolean + notBefore: + description: NotBefore defines whether to add the Not Before + information from the Validity part. + type: boolean + sans: + description: Sans defines whether to add the Subject Alternative + Name information from the Subject Alternative Name part. + type: boolean + serialNumber: + description: SerialNumber defines whether to add the client + serialNumber information. + type: boolean + subject: + description: Subject defines the client certificate subject + details to add to the X-Forwarded-Tls-Client-Cert-Info header. + properties: + commonName: + description: CommonName defines whether to add the organizationalUnit + information into the subject. + type: boolean + country: + description: Country defines whether to add the country + information into the subject. + type: boolean + domainComponent: + description: DomainComponent defines whether to add the + domainComponent information into the subject. + type: boolean + locality: + description: Locality defines whether to add the locality + information into the subject. + type: boolean + organization: + description: Organization defines whether to add the organization + information into the subject. + type: boolean + organizationalUnit: + description: OrganizationalUnit defines whether to add + the organizationalUnit information into the subject. + type: boolean + province: + description: Province defines whether to add the province + information into the subject. + type: boolean + serialNumber: + description: SerialNumber defines whether to add the serialNumber + information into the subject. + type: boolean + type: object + type: object + pem: + description: PEM sets the X-Forwarded-Tls-Client-Cert header with + the certificate. + type: boolean + type: object + plugin: + additionalProperties: + x-kubernetes-preserve-unknown-fields: true + description: |- + Plugin defines the middleware plugin configuration. + More info: https://doc.traefik.io/traefik/plugins/ + type: object + rateLimit: + description: |- + RateLimit holds the rate limit configuration. + This middleware ensures that services will receive a fair amount of requests, and allows one to define what fair is. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ratelimit/ + properties: + average: + description: |- + Average is the maximum rate, by default in requests/s, allowed for the given source. + It defaults to 0, which means no rate limiting. + The rate is actually defined by dividing Average by Period. So for a rate below 1req/s, + one needs to define a Period larger than a second. + format: int64 + type: integer + burst: + description: |- + Burst is the maximum number of requests allowed to arrive in the same arbitrarily small period of time. + It defaults to 1. + format: int64 + type: integer + period: + anyOf: + - type: integer + - type: string + description: |- + Period, in combination with Average, defines the actual maximum rate, such as: + r = Average / Period. It defaults to a second. + x-kubernetes-int-or-string: true + sourceCriterion: + description: |- + SourceCriterion defines what criterion is used to group requests as originating from a common source. + If several strategies are defined at the same time, an error will be raised. + If none are set, the default is to use the request's remote address field (as an ipStrategy). + properties: + ipStrategy: + description: |- + IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy + properties: + depth: + description: Depth tells Traefik to use the X-Forwarded-For + header and take the IP located at the depth position + (starting from the right). + type: integer + excludedIPs: + description: ExcludedIPs configures Traefik to scan the + X-Forwarded-For header and select the first IP not in + the list. + items: + type: string + type: array + type: object + requestHeaderName: + description: RequestHeaderName defines the name of the header + used to group incoming requests. + type: string + requestHost: + description: RequestHost defines whether to consider the request + Host as the source. + type: boolean + type: object + type: object + redirectRegex: + description: |- + RedirectRegex holds the redirect regex middleware configuration. + This middleware redirects a request using regex matching and replacement. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectregex/#regex + properties: + permanent: + description: Permanent defines whether the redirection is permanent + (301). + type: boolean + regex: + description: Regex defines the regex used to match and capture + elements from the request URL. + type: string + replacement: + description: Replacement defines how to modify the URL to have + the new target URL. + type: string + type: object + redirectScheme: + description: |- + RedirectScheme holds the redirect scheme middleware configuration. + This middleware redirects requests from a scheme/port to another. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectscheme/ + properties: + permanent: + description: Permanent defines whether the redirection is permanent + (301). + type: boolean + port: + description: Port defines the port of the new URL. + type: string + scheme: + description: Scheme defines the scheme of the new URL. + type: string + type: object + replacePath: + description: |- + ReplacePath holds the replace path middleware configuration. + This middleware replaces the path of the request URL and store the original path in an X-Replaced-Path header. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepath/ + properties: + path: + description: Path defines the path to use as replacement in the + request URL. + type: string + type: object + replacePathRegex: + description: |- + ReplacePathRegex holds the replace path regex middleware configuration. + This middleware replaces the path of a URL using regex matching and replacement. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepathregex/ + properties: + regex: + description: Regex defines the regular expression used to match + and capture the path from the request URL. + type: string + replacement: + description: Replacement defines the replacement path format, + which can include captured variables. + type: string + type: object + retry: + description: |- + Retry holds the retry middleware configuration. + This middleware reissues requests a given number of times to a backend server if that server does not reply. + As soon as the server answers, the middleware stops retrying, regardless of the response status. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/retry/ + properties: + attempts: + description: Attempts defines how many times the request should + be retried. + type: integer + initialInterval: + anyOf: + - type: integer + - type: string + description: |- + InitialInterval defines the first wait time in the exponential backoff series. + The maximum interval is calculated as twice the initialInterval. + If unspecified, requests will be retried immediately. + The value of initialInterval should be provided in seconds or as a valid duration format, + see https://pkg.go.dev/time#ParseDuration. + x-kubernetes-int-or-string: true + type: object + stripPrefix: + description: |- + StripPrefix holds the strip prefix middleware configuration. + This middleware removes the specified prefixes from the URL path. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefix/ + properties: + forceSlash: + description: |- + Deprecated: ForceSlash option is deprecated, please remove any usage of this option. + ForceSlash ensures that the resulting stripped path is not the empty string, by replacing it with / when necessary. + Default: true. + type: boolean + prefixes: + description: Prefixes defines the prefixes to strip from the request + URL. + items: + type: string + type: array + type: object + stripPrefixRegex: + description: |- + StripPrefixRegex holds the strip prefix regex middleware configuration. + This middleware removes the matching prefixes from the URL path. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefixregex/ + properties: + regex: + description: Regex defines the regular expression to match the + path prefix from the request URL. + items: + type: string + type: array + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true diff --git a/charts/traefik/traefik/30.0.2/crds/traefik.io_middlewaretcps.yaml b/charts/traefik/traefik/30.0.2/crds/traefik.io_middlewaretcps.yaml new file mode 100644 index 000000000..250ac1b12 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/traefik.io_middlewaretcps.yaml @@ -0,0 +1,87 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: middlewaretcps.traefik.io +spec: + group: traefik.io + names: + kind: MiddlewareTCP + listKind: MiddlewareTCPList + plural: middlewaretcps + singular: middlewaretcp + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + MiddlewareTCP is the CRD implementation of a Traefik TCP middleware. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/overview/ + 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: MiddlewareTCPSpec defines the desired state of a MiddlewareTCP. + properties: + inFlightConn: + description: InFlightConn defines the InFlightConn middleware configuration. + properties: + amount: + description: |- + Amount defines the maximum amount of allowed simultaneous connections. + The middleware closes the connection if there are already amount connections opened. + format: int64 + type: integer + type: object + ipAllowList: + description: |- + IPAllowList defines the IPAllowList middleware configuration. + This middleware accepts/refuses connections based on the client IP. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/ipallowlist/ + properties: + sourceRange: + description: SourceRange defines the allowed IPs (or ranges of + allowed IPs by using CIDR notation). + items: + type: string + type: array + type: object + ipWhiteList: + description: |- + IPWhiteList defines the IPWhiteList middleware configuration. + This middleware accepts/refuses connections based on the client IP. + Deprecated: please use IPAllowList instead. + More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/ipwhitelist/ + properties: + sourceRange: + description: SourceRange defines the allowed IPs (or ranges of + allowed IPs by using CIDR notation). + items: + type: string + type: array + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true diff --git a/charts/traefik/traefik/30.0.2/crds/traefik.io_serverstransports.yaml b/charts/traefik/traefik/30.0.2/crds/traefik.io_serverstransports.yaml new file mode 100644 index 000000000..287943fbf --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/traefik.io_serverstransports.yaml @@ -0,0 +1,139 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: serverstransports.traefik.io +spec: + group: traefik.io + names: + kind: ServersTransport + listKind: ServersTransportList + plural: serverstransports + singular: serverstransport + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + ServersTransport is the CRD implementation of a ServersTransport. + If no serversTransport is specified, the default@internal will be used. + The default@internal serversTransport is created from the static configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_1 + 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: ServersTransportSpec defines the desired state of a ServersTransport. + properties: + certificatesSecrets: + description: CertificatesSecrets defines a list of secret storing + client certificates for mTLS. + items: + type: string + type: array + disableHTTP2: + description: DisableHTTP2 disables HTTP/2 for connections with backend + servers. + type: boolean + forwardingTimeouts: + description: ForwardingTimeouts defines the timeouts for requests + forwarded to the backend servers. + properties: + dialTimeout: + anyOf: + - type: integer + - type: string + description: DialTimeout is the amount of time to wait until a + connection to a backend server can be established. + x-kubernetes-int-or-string: true + idleConnTimeout: + anyOf: + - type: integer + - type: string + description: IdleConnTimeout is the maximum period for which an + idle HTTP keep-alive connection will remain open before closing + itself. + x-kubernetes-int-or-string: true + pingTimeout: + anyOf: + - type: integer + - type: string + description: PingTimeout is the timeout after which the HTTP/2 + connection will be closed if a response to ping is not received. + x-kubernetes-int-or-string: true + readIdleTimeout: + anyOf: + - type: integer + - type: string + description: ReadIdleTimeout is the timeout after which a health + check using ping frame will be carried out if no frame is received + on the HTTP/2 connection. + x-kubernetes-int-or-string: true + responseHeaderTimeout: + anyOf: + - type: integer + - type: string + description: ResponseHeaderTimeout is the amount of time to wait + for a server's response headers after fully writing the request + (including its body, if any). + x-kubernetes-int-or-string: true + type: object + insecureSkipVerify: + description: InsecureSkipVerify disables SSL certificate verification. + type: boolean + maxIdleConnsPerHost: + description: MaxIdleConnsPerHost controls the maximum idle (keep-alive) + to keep per-host. + type: integer + peerCertURI: + description: PeerCertURI defines the peer cert URI used to match against + SAN URI during the peer certificate verification. + type: string + rootCAsSecrets: + description: RootCAsSecrets defines a list of CA secret used to validate + self-signed certificate. + items: + type: string + type: array + serverName: + description: ServerName defines the server name used to contact the + server. + type: string + spiffe: + description: Spiffe defines the SPIFFE configuration. + properties: + ids: + description: IDs defines the allowed SPIFFE IDs (takes precedence + over the SPIFFE TrustDomain). + items: + type: string + type: array + trustDomain: + description: TrustDomain defines the allowed SPIFFE trust domain. + type: string + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true diff --git a/charts/traefik/traefik/30.0.2/crds/traefik.io_serverstransporttcps.yaml b/charts/traefik/traefik/30.0.2/crds/traefik.io_serverstransporttcps.yaml new file mode 100644 index 000000000..b255d3296 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/traefik.io_serverstransporttcps.yaml @@ -0,0 +1,120 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: serverstransporttcps.traefik.io +spec: + group: traefik.io + names: + kind: ServersTransportTCP + listKind: ServersTransportTCPList + plural: serverstransporttcps + singular: serverstransporttcp + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + ServersTransportTCP is the CRD implementation of a TCPServersTransport. + If no tcpServersTransport is specified, a default one named default@internal will be used. + The default@internal tcpServersTransport can be configured in the static configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_3 + 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: ServersTransportTCPSpec defines the desired state of a ServersTransportTCP. + properties: + dialKeepAlive: + anyOf: + - type: integer + - type: string + description: DialKeepAlive is the interval between keep-alive probes + for an active network connection. If zero, keep-alive probes are + sent with a default value (currently 15 seconds), if supported by + the protocol and operating system. Network protocols or operating + systems that do not support keep-alives ignore this field. If negative, + keep-alive probes are disabled. + x-kubernetes-int-or-string: true + dialTimeout: + anyOf: + - type: integer + - type: string + description: DialTimeout is the amount of time to wait until a connection + to a backend server can be established. + x-kubernetes-int-or-string: true + terminationDelay: + anyOf: + - type: integer + - type: string + description: TerminationDelay defines the delay to wait before fully + terminating the connection, after one connected peer has closed + its writing capability. + x-kubernetes-int-or-string: true + tls: + description: TLS defines the TLS configuration + properties: + certificatesSecrets: + description: CertificatesSecrets defines a list of secret storing + client certificates for mTLS. + items: + type: string + type: array + insecureSkipVerify: + description: InsecureSkipVerify disables TLS certificate verification. + type: boolean + peerCertURI: + description: |- + MaxIdleConnsPerHost controls the maximum idle (keep-alive) to keep per-host. + PeerCertURI defines the peer cert URI used to match against SAN URI during the peer certificate verification. + type: string + rootCAsSecrets: + description: RootCAsSecrets defines a list of CA secret used to + validate self-signed certificates. + items: + type: string + type: array + serverName: + description: ServerName defines the server name used to contact + the server. + type: string + spiffe: + description: Spiffe defines the SPIFFE configuration. + properties: + ids: + description: IDs defines the allowed SPIFFE IDs (takes precedence + over the SPIFFE TrustDomain). + items: + type: string + type: array + trustDomain: + description: TrustDomain defines the allowed SPIFFE trust + domain. + type: string + type: object + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true diff --git a/charts/traefik/traefik/30.0.2/crds/traefik.io_tlsoptions.yaml b/charts/traefik/traefik/30.0.2/crds/traefik.io_tlsoptions.yaml new file mode 100644 index 000000000..2380e8ef6 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/traefik.io_tlsoptions.yaml @@ -0,0 +1,114 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: tlsoptions.traefik.io +spec: + group: traefik.io + names: + kind: TLSOption + listKind: TLSOptionList + plural: tlsoptions + singular: tlsoption + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + TLSOption is the CRD implementation of a Traefik TLS Option, allowing to configure some parameters of the TLS connection. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options + 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: TLSOptionSpec defines the desired state of a TLSOption. + properties: + alpnProtocols: + description: |- + ALPNProtocols defines the list of supported application level protocols for the TLS handshake, in order of preference. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#alpn-protocols + items: + type: string + type: array + cipherSuites: + description: |- + CipherSuites defines the list of supported cipher suites for TLS versions up to TLS 1.2. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#cipher-suites + items: + type: string + type: array + clientAuth: + description: ClientAuth defines the server's policy for TLS Client + Authentication. + properties: + clientAuthType: + description: ClientAuthType defines the client authentication + type to apply. + enum: + - NoClientCert + - RequestClientCert + - RequireAnyClientCert + - VerifyClientCertIfGiven + - RequireAndVerifyClientCert + type: string + secretNames: + description: SecretNames defines the names of the referenced Kubernetes + Secret storing certificate details. + items: + type: string + type: array + type: object + curvePreferences: + description: |- + CurvePreferences defines the preferred elliptic curves in a specific order. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#curve-preferences + items: + type: string + type: array + maxVersion: + description: |- + MaxVersion defines the maximum TLS version that Traefik will accept. + Possible values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13. + Default: None. + type: string + minVersion: + description: |- + MinVersion defines the minimum TLS version that Traefik will accept. + Possible values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13. + Default: VersionTLS10. + type: string + preferServerCipherSuites: + description: |- + PreferServerCipherSuites defines whether the server chooses a cipher suite among his own instead of among the client's. + It is enabled automatically when minVersion or maxVersion is set. + Deprecated: https://github.com/golang/go/issues/45430 + type: boolean + sniStrict: + description: SniStrict defines whether Traefik allows connections + from clients connections that do not specify a server_name extension. + type: boolean + type: object + required: + - metadata + - spec + type: object + served: true + storage: true diff --git a/charts/traefik/traefik/30.0.2/crds/traefik.io_tlsstores.yaml b/charts/traefik/traefik/30.0.2/crds/traefik.io_tlsstores.yaml new file mode 100644 index 000000000..15c4951ea --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/traefik.io_tlsstores.yaml @@ -0,0 +1,97 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: tlsstores.traefik.io +spec: + group: traefik.io + names: + kind: TLSStore + listKind: TLSStoreList + plural: tlsstores + singular: tlsstore + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + TLSStore is the CRD implementation of a Traefik TLS Store. + For the time being, only the TLSStore named default is supported. + This means that you cannot have two stores that are named default in different Kubernetes namespaces. + More info: https://doc.traefik.io/traefik/v3.0/https/tls/#certificates-stores + 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: TLSStoreSpec defines the desired state of a TLSStore. + properties: + certificates: + description: Certificates is a list of secret names, each secret holding + a key/certificate pair to add to the store. + items: + description: Certificate holds a secret name for the TLSStore resource. + properties: + secretName: + description: SecretName is the name of the referenced Kubernetes + Secret to specify the certificate details. + type: string + required: + - secretName + type: object + type: array + defaultCertificate: + description: DefaultCertificate defines the default certificate configuration. + properties: + secretName: + description: SecretName is the name of the referenced Kubernetes + Secret to specify the certificate details. + type: string + required: + - secretName + type: object + defaultGeneratedCert: + description: DefaultGeneratedCert defines the default generated certificate + configuration. + properties: + domain: + description: Domain is the domain definition for the DefaultCertificate. + properties: + main: + description: Main defines the main domain name. + type: string + sans: + description: SANs defines the subject alternative domain names. + items: + type: string + type: array + type: object + resolver: + description: Resolver is the name of the resolver that will be + used to issue the DefaultCertificate. + type: string + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true diff --git a/charts/traefik/traefik/30.0.2/crds/traefik.io_traefikservices.yaml b/charts/traefik/traefik/30.0.2/crds/traefik.io_traefikservices.yaml new file mode 100644 index 000000000..7a0f7daf3 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/crds/traefik.io_traefikservices.yaml @@ -0,0 +1,639 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: traefikservices.traefik.io +spec: + group: traefik.io + names: + kind: TraefikService + listKind: TraefikServiceList + plural: traefikservices + singular: traefikservice + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + TraefikService is the CRD implementation of a Traefik Service. + TraefikService object allows to: + - Apply weight to Services on load-balancing + - Mirror traffic on services + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-traefikservice + 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: TraefikServiceSpec defines the desired state of a TraefikService. + properties: + mirroring: + description: Mirroring defines the Mirroring service configuration. + properties: + healthCheck: + description: Healthcheck defines health checks for ExternalName + services. + properties: + followRedirects: + description: |- + FollowRedirects defines whether redirects should be followed during the health check calls. + Default: true + type: boolean + headers: + additionalProperties: + type: string + description: Headers defines custom headers to be sent to + the health check endpoint. + type: object + hostname: + description: Hostname defines the value of hostname in the + Host header of the health check request. + type: string + interval: + anyOf: + - type: integer + - type: string + description: |- + Interval defines the frequency of the health check calls. + Default: 30s + x-kubernetes-int-or-string: true + method: + description: Method defines the healthcheck method. + type: string + mode: + description: |- + Mode defines the health check mode. + If defined to grpc, will use the gRPC health check protocol to probe the server. + Default: http + type: string + path: + description: Path defines the server URL path for the health + check endpoint. + type: string + port: + description: Port defines the server URL port for the health + check endpoint. + type: integer + scheme: + description: Scheme replaces the server URL scheme for the + health check endpoint. + type: string + status: + description: Status defines the expected HTTP status code + of the response to the health check request. + type: integer + timeout: + anyOf: + - type: integer + - type: string + description: |- + Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. + Default: 5s + x-kubernetes-int-or-string: true + type: object + kind: + description: Kind defines the kind of the Service. + enum: + - Service + - TraefikService + type: string + maxBodySize: + description: |- + MaxBodySize defines the maximum size allowed for the body of the request. + If the body is larger, the request is not mirrored. + Default value is -1, which means unlimited size. + format: int64 + type: integer + mirrors: + description: Mirrors defines the list of mirrors where Traefik + will duplicate the traffic. + items: + description: MirrorService holds the mirror configuration. + properties: + healthCheck: + description: Healthcheck defines health checks for ExternalName + services. + properties: + followRedirects: + description: |- + FollowRedirects defines whether redirects should be followed during the health check calls. + Default: true + type: boolean + headers: + additionalProperties: + type: string + description: Headers defines custom headers to be sent + to the health check endpoint. + type: object + hostname: + description: Hostname defines the value of hostname + in the Host header of the health check request. + type: string + interval: + anyOf: + - type: integer + - type: string + description: |- + Interval defines the frequency of the health check calls. + Default: 30s + x-kubernetes-int-or-string: true + method: + description: Method defines the healthcheck method. + type: string + mode: + description: |- + Mode defines the health check mode. + If defined to grpc, will use the gRPC health check protocol to probe the server. + Default: http + type: string + path: + description: Path defines the server URL path for the + health check endpoint. + type: string + port: + description: Port defines the server URL port for the + health check endpoint. + type: integer + scheme: + description: Scheme replaces the server URL scheme for + the health check endpoint. + type: string + status: + description: Status defines the expected HTTP status + code of the response to the health check request. + type: integer + timeout: + anyOf: + - type: integer + - type: string + description: |- + Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. + Default: 5s + x-kubernetes-int-or-string: true + type: object + kind: + description: Kind defines the kind of the Service. + enum: + - Service + - TraefikService + type: string + name: + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Kubernetes Service or TraefikService. + type: string + nativeLB: + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. + type: boolean + nodePortLB: + description: |- + NodePortLB controls, when creating the load-balancer, + whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort. + It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes. + By default, NodePortLB is false. + type: boolean + passHostHeader: + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. + By default, passHostHeader is true. + type: boolean + percent: + description: |- + Percent defines the part of the traffic to mirror. + Supported values: 0 to 100. + type: integer + port: + anyOf: + - type: integer + - type: string + description: |- + Port defines the port of a Kubernetes Service. + This can be a reference to a named port. + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding defines how Traefik forwards + the response from the upstream Kubernetes Service to the + client. + properties: + flushInterval: + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms + type: string + type: object + scheme: + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. + type: string + serversTransport: + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. + type: string + sticky: + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions + properties: + cookie: + description: Cookie defines the sticky cookie configuration. + properties: + httpOnly: + description: HTTPOnly defines whether the cookie + can be accessed by client-side APIs, such as JavaScript. + type: boolean + maxAge: + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. + type: integer + name: + description: Name defines the Cookie name. + type: string + sameSite: + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite + type: string + secure: + description: Secure defines whether the cookie can + only be transmitted over an encrypted connection + (i.e. HTTPS). + type: boolean + type: object + type: object + strategy: + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. + type: string + weight: + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round Robin). + type: integer + required: + - name + type: object + type: array + name: + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Kubernetes Service or TraefikService. + type: string + nativeLB: + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. + type: boolean + nodePortLB: + description: |- + NodePortLB controls, when creating the load-balancer, + whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort. + It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes. + By default, NodePortLB is false. + type: boolean + passHostHeader: + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. + By default, passHostHeader is true. + type: boolean + port: + anyOf: + - type: integer + - type: string + description: |- + Port defines the port of a Kubernetes Service. + This can be a reference to a named port. + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding defines how Traefik forwards the + response from the upstream Kubernetes Service to the client. + properties: + flushInterval: + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms + type: string + type: object + scheme: + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. + type: string + serversTransport: + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. + type: string + sticky: + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions + properties: + cookie: + description: Cookie defines the sticky cookie configuration. + properties: + httpOnly: + description: HTTPOnly defines whether the cookie can be + accessed by client-side APIs, such as JavaScript. + type: boolean + maxAge: + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. + type: integer + name: + description: Name defines the Cookie name. + type: string + sameSite: + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite + type: string + secure: + description: Secure defines whether the cookie can only + be transmitted over an encrypted connection (i.e. HTTPS). + type: boolean + type: object + type: object + strategy: + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. + type: string + weight: + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round Robin). + type: integer + required: + - name + type: object + weighted: + description: Weighted defines the Weighted Round Robin configuration. + properties: + services: + description: Services defines the list of Kubernetes Service and/or + TraefikService to load-balance, with weight. + items: + description: Service defines an upstream HTTP service to proxy + traffic to. + properties: + healthCheck: + description: Healthcheck defines health checks for ExternalName + services. + properties: + followRedirects: + description: |- + FollowRedirects defines whether redirects should be followed during the health check calls. + Default: true + type: boolean + headers: + additionalProperties: + type: string + description: Headers defines custom headers to be sent + to the health check endpoint. + type: object + hostname: + description: Hostname defines the value of hostname + in the Host header of the health check request. + type: string + interval: + anyOf: + - type: integer + - type: string + description: |- + Interval defines the frequency of the health check calls. + Default: 30s + x-kubernetes-int-or-string: true + method: + description: Method defines the healthcheck method. + type: string + mode: + description: |- + Mode defines the health check mode. + If defined to grpc, will use the gRPC health check protocol to probe the server. + Default: http + type: string + path: + description: Path defines the server URL path for the + health check endpoint. + type: string + port: + description: Port defines the server URL port for the + health check endpoint. + type: integer + scheme: + description: Scheme replaces the server URL scheme for + the health check endpoint. + type: string + status: + description: Status defines the expected HTTP status + code of the response to the health check request. + type: integer + timeout: + anyOf: + - type: integer + - type: string + description: |- + Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. + Default: 5s + x-kubernetes-int-or-string: true + type: object + kind: + description: Kind defines the kind of the Service. + enum: + - Service + - TraefikService + type: string + name: + description: |- + Name defines the name of the referenced Kubernetes Service or TraefikService. + The differentiation between the two is specified in the Kind field. + type: string + namespace: + description: Namespace defines the namespace of the referenced + Kubernetes Service or TraefikService. + type: string + nativeLB: + description: |- + NativeLB controls, when creating the load-balancer, + whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. + The Kubernetes Service itself does load-balance to the pods. + By default, NativeLB is false. + type: boolean + nodePortLB: + description: |- + NodePortLB controls, when creating the load-balancer, + whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort. + It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes. + By default, NodePortLB is false. + type: boolean + passHostHeader: + description: |- + PassHostHeader defines whether the client Host header is forwarded to the upstream Kubernetes Service. + By default, passHostHeader is true. + type: boolean + port: + anyOf: + - type: integer + - type: string + description: |- + Port defines the port of a Kubernetes Service. + This can be a reference to a named port. + x-kubernetes-int-or-string: true + responseForwarding: + description: ResponseForwarding defines how Traefik forwards + the response from the upstream Kubernetes Service to the + client. + properties: + flushInterval: + description: |- + FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body. + A negative value means to flush immediately after each write to the client. + This configuration is ignored when ReverseProxy recognizes a response as a streaming response; + for such responses, writes are flushed to the client immediately. + Default: 100ms + type: string + type: object + scheme: + description: |- + Scheme defines the scheme to use for the request to the upstream Kubernetes Service. + It defaults to https when Kubernetes Service port is 443, http otherwise. + type: string + serversTransport: + description: |- + ServersTransport defines the name of ServersTransport resource to use. + It allows to configure the transport between Traefik and your servers. + Can only be used on a Kubernetes Service. + type: string + sticky: + description: |- + Sticky defines the sticky sessions configuration. + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions + properties: + cookie: + description: Cookie defines the sticky cookie configuration. + properties: + httpOnly: + description: HTTPOnly defines whether the cookie + can be accessed by client-side APIs, such as JavaScript. + type: boolean + maxAge: + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. + type: integer + name: + description: Name defines the Cookie name. + type: string + sameSite: + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite + type: string + secure: + description: Secure defines whether the cookie can + only be transmitted over an encrypted connection + (i.e. HTTPS). + type: boolean + type: object + type: object + strategy: + description: |- + Strategy defines the load balancing strategy between the servers. + RoundRobin is the only supported value at the moment. + type: string + weight: + description: |- + Weight defines the weight and should only be specified when Name references a TraefikService object + (and to be precise, one that embeds a Weighted Round Robin). + type: integer + required: + - name + type: object + type: array + sticky: + description: |- + Sticky defines whether sticky sessions are enabled. + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#stickiness-and-load-balancing + properties: + cookie: + description: Cookie defines the sticky cookie configuration. + properties: + httpOnly: + description: HTTPOnly defines whether the cookie can be + accessed by client-side APIs, such as JavaScript. + type: boolean + maxAge: + description: |- + MaxAge indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + When set to zero, the cookie never expires. + type: integer + name: + description: Name defines the Cookie name. + type: string + sameSite: + description: |- + SameSite defines the same site policy. + More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite + type: string + secure: + description: Secure defines whether the cookie can only + be transmitted over an encrypted connection (i.e. HTTPS). + type: boolean + type: object + type: object + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true diff --git a/charts/traefik/traefik/30.0.2/templates/NOTES.txt b/charts/traefik/traefik/30.0.2/templates/NOTES.txt new file mode 100644 index 000000000..a1a10bfb3 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/NOTES.txt @@ -0,0 +1,36 @@ + + +{{ .Release.Name }} with {{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }} has been deployed successfully on {{ template "traefik.namespace" . }} namespace ! + +{{- if .Values.persistence }} +{{- if and .Values.persistence.enabled (empty .Values.deployment.initContainer)}} + +🚨 When enabling persistence for certificates, permissions on acme.json can be +lost when Traefik restarts. You can ensure correct permissions with an +initContainer. See https://github.com/traefik/traefik-helm-chart/blob/master/EXAMPLES.md#use-traefik-native-lets-encrypt-integration-without-cert-manager +for more info. 🚨 + +{{- end }} +{{- end }} +{{- with .Values.providers.kubernetesCRD.labelSelector }} + {{- $labelsApplied := include "traefik.labels" $ }} + {{- $labelSelectors := regexSplit "," . -1 }} + {{- range $labelSelectors }} + {{- $labelSelectorRaw := regexSplit "=" . -1 }} + {{- $labelSelector := printf "%s: %s" (first $labelSelectorRaw) (last $labelSelectorRaw) }} + {{- if not (contains $labelSelector $labelsApplied) }} +🚨 Resources populated with this chart don't match with labelSelector `{{.}}` applied on kubernetesCRD provider 🚨 + {{- end }} + {{- end }} +{{- end }} +{{- with .Values.providers.kubernetesIngress.labelSelector }} + {{- $labelsApplied := include "traefik.labels" $ }} + {{- $labelSelectors := regexSplit "," . -1 }} + {{- range $labelSelectors }} + {{- $labelSelectorRaw := regexSplit "=" . -1 }} + {{- $labelSelector := printf "%s: %s" (first $labelSelectorRaw) (last $labelSelectorRaw) }} + {{- if not (contains $labelSelector $labelsApplied) }} +🚨 Resources populated with this chart don't match with labelSelector `{{.}}` applied on kubernetesIngress provider 🚨 + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/traefik/traefik/30.0.2/templates/_helpers.tpl b/charts/traefik/traefik/30.0.2/templates/_helpers.tpl new file mode 100644 index 000000000..2183f84ab --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/_helpers.tpl @@ -0,0 +1,161 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Expand the name of the chart. +*/}} +{{- define "traefik.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "traefik.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create the chart image name. +*/}} +{{- define "traefik.image-name" -}} +{{- printf "%s/%s:%s" .Values.image.registry .Values.image.repository (.Values.image.tag | default .Chart.AppVersion) }} +{{- 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 "traefik.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 -}} + +{{/* +Allow customization of the instance label value. +*/}} +{{- define "traefik.instance-name" -}} +{{- default (printf "%s-%s" .Release.Name .Release.Namespace) .Values.instanceLabelOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* Shared labels used for selector*/}} +{{/* This is an immutable field: this should not change between upgrade */}} +{{- define "traefik.labelselector" -}} +app.kubernetes.io/name: {{ template "traefik.name" . }} +app.kubernetes.io/instance: {{ template "traefik.instance-name" . }} +{{- end }} + +{{/* Shared labels used in metada */}} +{{- define "traefik.labels" -}} +{{ include "traefik.labelselector" . }} +helm.sh/chart: {{ template "traefik.chart" . }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- with .Values.commonLabels }} +{{ toYaml . }} +{{- end }} +{{- end }} + +{{/* +Construct the namespace for all namespaced resources +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +Preserve the default behavior of the Release namespace if no override is provided +*/}} +{{- define "traefik.namespace" -}} +{{- if .Values.namespaceOverride -}} +{{- .Values.namespaceOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- .Release.Namespace -}} +{{- end -}} +{{- end -}} + +{{/* +The name of the service account to use +*/}} +{{- define "traefik.serviceAccountName" -}} +{{- default (include "traefik.fullname" .) .Values.serviceAccount.name -}} +{{- end -}} + +{{/* +The name of the ClusterRole and ClusterRoleBinding to use. +Adds the namespace to name to prevent duplicate resource names when there +are multiple namespaced releases with the same release name. +*/}} +{{- define "traefik.clusterRoleName" -}} +{{- (printf "%s-%s" (include "traefik.fullname" .) .Release.Namespace) | trunc 63 | trimSuffix "-" }} +{{- end -}} + +{{/* +Construct the path for the providers.kubernetesingress.ingressendpoint.publishedservice. +By convention this will simply use the / to match the name of the +service generated. +Users can provide an override for an explicit service they want bound via `.Values.providers.kubernetesIngress.publishedService.pathOverride` +*/}} +{{- define "providers.kubernetesIngress.publishedServicePath" -}} +{{- $defServiceName := printf "%s/%s" .Release.Namespace (include "traefik.fullname" .) -}} +{{- $servicePath := default $defServiceName .Values.providers.kubernetesIngress.publishedService.pathOverride }} +{{- print $servicePath | trimSuffix "-" -}} +{{- end -}} + +{{/* +Construct a comma-separated list of whitelisted namespaces +*/}} +{{- define "providers.kubernetesCRD.namespaces" -}} +{{- default (include "traefik.namespace" .) (join "," .Values.providers.kubernetesCRD.namespaces) }} +{{- end -}} +{{- define "providers.kubernetesGateway.namespaces" -}} +{{- default (include "traefik.namespace" .) (join "," .Values.providers.kubernetesGateway.namespaces) }} +{{- end -}} +{{- define "providers.kubernetesIngress.namespaces" -}} +{{- default (include "traefik.namespace" .) (join "," .Values.providers.kubernetesIngress.namespaces) }} +{{- end -}} + +{{/* +Renders a complete tree, even values that contains template. +*/}} +{{- define "traefik.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{ else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} + +{{- define "imageVersion" -}} +{{/* +Traefik hub is based on v3.1 (v3.0 before v3.3.1) of traefik proxy, so this is a hack to avoid to much complexity in RBAC management which are +based on semverCompare +*/}} +{{- if $.Values.hub.token -}} +{{ if and (regexMatch "v[0-9]+.[0-9]+.[0-9]+" (default "" $.Values.image.tag)) (semverCompare "= v1.19" -}} + {{- end }} + topologySpreadConstraints: + {{- tpl (toYaml .Values.topologySpreadConstraints) . | nindent 8 }} + {{- end }} +{{ end -}} diff --git a/charts/traefik/traefik/30.0.2/templates/_service-metrics.tpl b/charts/traefik/traefik/30.0.2/templates/_service-metrics.tpl new file mode 100644 index 000000000..d16a3629d --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/_service-metrics.tpl @@ -0,0 +1,25 @@ +{{- define "traefik.metrics-service-metadata" }} + labels: + {{- include "traefik.metricsservicelabels" . | nindent 4 -}} + {{- with .Values.metrics.prometheus.service.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} + +{{/* Labels used for metrics-relevant selector*/}} +{{/* This is an immutable field: this should not change between upgrade */}} +{{- define "traefik.metricslabelselector" -}} +{{- include "traefik.labelselector" . }} +app.kubernetes.io/component: metrics +{{- end }} + +{{/* Shared labels used in metadata of metrics-service and servicemonitor */}} +{{- define "traefik.metricsservicelabels" -}} +{{ include "traefik.metricslabelselector" . }} +helm.sh/chart: {{ template "traefik.chart" . }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- with .Values.commonLabels }} +{{ toYaml . }} +{{- end }} +{{- end }} + diff --git a/charts/traefik/traefik/30.0.2/templates/_service.tpl b/charts/traefik/traefik/30.0.2/templates/_service.tpl new file mode 100644 index 000000000..03004e57c --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/_service.tpl @@ -0,0 +1,84 @@ +{{- define "traefik.service-name" -}} +{{- $fullname := printf "%s-%s" (include "traefik.fullname" .root) .name -}} +{{- if eq .name "default" -}} +{{- $fullname = include "traefik.fullname" .root -}} +{{- end -}} + +{{- if ge (len $fullname) 60 -}} # 64 - 4 (udp-postfix) = 60 + {{- fail "ERROR: Cannot create a service whose full name contains more than 60 characters" -}} +{{- end -}} + +{{- $fullname -}} +{{- end -}} + +{{- define "traefik.service-metadata" }} + labels: + {{- include "traefik.labels" .root | nindent 4 -}} + {{- with .service.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} + +{{- define "traefik.service-spec" -}} + {{- $type := default "LoadBalancer" .service.type }} + type: {{ $type }} + {{- with .service.loadBalancerClass }} + loadBalancerClass: {{ . }} + {{- end}} + {{- with .service.spec }} + {{- toYaml . | nindent 2 }} + {{- end }} + selector: + {{- include "traefik.labelselector" .root | nindent 4 }} + {{- if eq $type "LoadBalancer" }} + {{- with .service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- toYaml . | nindent 2 }} + {{- end -}} + {{- end -}} + {{- with .service.externalIPs }} + externalIPs: + {{- toYaml . | nindent 2 }} + {{- end -}} + {{- with .service.ipFamilyPolicy }} + ipFamilyPolicy: {{ . }} + {{- end }} + {{- with .service.ipFamilies }} + ipFamilies: + {{- toYaml . | nindent 2 }} + {{- end -}} +{{- end }} + +{{- define "traefik.service-ports" }} + {{- range $name, $config := .ports }} + {{- if (index (default dict $config.expose) $.serviceName) }} + {{- $port := default $config.port $config.exposedPort }} + {{- if empty $port }} + {{- fail (print "ERROR: Cannot create " (trim $name) " port on Service without .port or .exposedPort") }} + {{- end }} + - port: {{ $port }} + name: {{ $name | quote }} + targetPort: {{ default $name $config.targetPort }} + protocol: {{ default "TCP" $config.protocol }} + {{- if $config.nodePort }} + nodePort: {{ $config.nodePort }} + {{- end }} + {{- if $config.appProtocol }} + appProtocol: {{ $config.appProtocol }} + {{- end }} + {{- if ($config.http3).enabled }} + {{- $http3Port := default $config.exposedPort $config.http3.advertisedPort }} + - port: {{ $http3Port }} + name: "{{ $name }}-http3" + targetPort: {{ $name }}-http3 + protocol: UDP + {{- if $config.nodePort }} + nodePort: {{ $config.nodePort }} + {{- end }} + {{- if $config.appProtocol }} + appProtocol: {{ $config.appProtocol }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/traefik/traefik/30.0.2/templates/daemonset.yaml b/charts/traefik/traefik/30.0.2/templates/daemonset.yaml new file mode 100644 index 000000000..5be6a0a25 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/daemonset.yaml @@ -0,0 +1,50 @@ +{{- if and .Values.deployment.enabled (eq .Values.deployment.kind "DaemonSet") -}} + {{- with .Values.additionalArguments -}} + {{- range . -}} + {{- if contains ".acme." . -}} + {{- fail (printf "ACME functionality is not supported when running Traefik as a DaemonSet") -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- if eq (default .Chart.AppVersion .Values.image.tag) "latest" }} + {{- fail "\n\n ERROR: latest tag should not be used" }} + {{- end }} + {{- with .Values.updateStrategy }} + {{- if eq (.type) "RollingUpdate" }} + {{- if not (contains "%" (toString .rollingUpdate.maxUnavailable)) }} + {{- if and ($.Values.hostNetwork) (lt (float64 .rollingUpdate.maxUnavailable) 1.0) }} + {{- fail "maxUnavailable should be greater than 1 when using hostNetwork." }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: {{ template "traefik.fullname" . }} + namespace: {{ template "traefik.namespace" . }} + labels: + {{- include "traefik.labels" . | nindent 4 }} + {{- with .Values.deployment.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- if and .Values.providers.file.enabled (not .Values.providers.file.watch) }} + checksum/traefik-dynamic-conf: {{ include (print $.Template.BasePath "/provider-file-cm.yaml") . | sha256sum }} + {{- end }} + {{- with .Values.deployment.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + selector: + matchLabels: + {{- include "traefik.labelselector" . | nindent 6 }} + updateStrategy: {{ toYaml .Values.updateStrategy | nindent 4 }} + minReadySeconds: {{ .Values.deployment.minReadySeconds }} + {{- if .Values.deployment.revisionHistoryLimit }} + revisionHistoryLimit: {{ .Values.deployment.revisionHistoryLimit }} + {{- end }} + template: {{ template "traefik.podTemplate" . }} +{{- end -}} diff --git a/charts/traefik/traefik/30.0.2/templates/deployment.yaml b/charts/traefik/traefik/30.0.2/templates/deployment.yaml new file mode 100644 index 000000000..3e9c8ad78 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/deployment.yaml @@ -0,0 +1,54 @@ +{{/* check helm version */}} +{{- if (semverCompare "= 3.9.0 is required" -}} +{{- end -}} + +{{- if and .Values.deployment.enabled (eq .Values.deployment.kind "Deployment") -}} + {{- if gt (int .Values.deployment.replicas) 1 -}} + {{- with .Values.additionalArguments -}} + {{- range . -}} + {{- if contains ".acme." . -}} + {{- fail (printf "You can not enable acme if you set more than one traefik replica") -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- if eq (default .Chart.AppVersion .Values.image.tag) "latest" }} + {{- fail "\n\n ERROR: latest tag should not be used" }} + {{- end }} + +{{- if ne (typeOf .Values.experimental.plugins) "map[string]interface {}" }} + {{- fail (printf "ERROR: .Values.experimental.plugins should be a map (%s provided) !" (typeOf .Values.experimental.plugins)) }} +{{- end }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "traefik.fullname" . }} + namespace: {{ template "traefik.namespace" . }} + labels: + {{- include "traefik.labels" . | nindent 4 }} + {{- with .Values.deployment.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- if and .Values.providers.file.enabled (not .Values.providers.file.watch) }} + checksum/traefik-dynamic-conf: {{ include (print $.Template.BasePath "/provider-file-cm.yaml") . | sha256sum }} + {{- end }} + {{- with .Values.deployment.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ default 1 .Values.deployment.replicas }} + {{- end }} + {{- if .Values.deployment.revisionHistoryLimit }} + revisionHistoryLimit: {{ .Values.deployment.revisionHistoryLimit }} + {{- end }} + selector: + matchLabels: + {{- include "traefik.labelselector" . | nindent 6 }} + strategy: {{ toYaml .Values.updateStrategy | nindent 4 }} + minReadySeconds: {{ .Values.deployment.minReadySeconds }} + template: {{ template "traefik.podTemplate" . }} +{{- end -}} diff --git a/charts/traefik/traefik/30.0.2/templates/extra-objects.yaml b/charts/traefik/traefik/30.0.2/templates/extra-objects.yaml new file mode 100644 index 000000000..fb38e9773 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/extra-objects.yaml @@ -0,0 +1,4 @@ +{{- range .Values.extraObjects }} +--- +{{ include "traefik.render" (dict "value" . "context" $) }} +{{- end }} diff --git a/charts/traefik/traefik/30.0.2/templates/gateway.yaml b/charts/traefik/traefik/30.0.2/templates/gateway.yaml new file mode 100644 index 000000000..ab51c9204 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/gateway.yaml @@ -0,0 +1,52 @@ +{{- if and (.Values.gateway).enabled (.Values.providers.kubernetesGateway).enabled }} + {{- if not .Values.gateway.listeners }} + {{- fail "ERROR: gateway must have at least one listener or should be disabled" }} + {{- end }} +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: {{ default "traefik-gateway" .Values.gateway.name }} + namespace: {{ template "traefik.namespace" . }} + labels: + {{- include "traefik.labels" . | nindent 4 }} + {{- with .Values.gateway.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + gatewayClassName: {{ default "traefik" .Values.gatewayClass.name }} + listeners: + {{- range $name, $config := .Values.gateway.listeners }} + - name: {{ $name }} + {{ $found := false }} + {{- range $portName, $portConfig := $.Values.ports -}} + {{- if eq $portConfig.port $config.port -}} + {{ $found = true }} + {{- end -}} + {{- end -}} + {{ if not $found }} + {{- fail (printf "ERROR: port %0.f is not declared in ports" .port ) }} + {{- end -}} + port: {{ .port }} + protocol: {{ .protocol }} + {{- with .hostname }} + hostname: {{ . }} + {{- end }} + {{- with .namespacePolicy }} + allowedRoutes: + namespaces: + from: {{ . }} + {{- end }} + {{ if or .certificateRefs .mode }} + tls: + {{ with .mode }} + mode: {{ . }} + {{- end }} + {{ with .certificateRefs }} + certificateRefs: + {{- toYaml . | nindent 10 }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/traefik/traefik/30.0.2/templates/gatewayclass.yaml b/charts/traefik/traefik/30.0.2/templates/gatewayclass.yaml new file mode 100644 index 000000000..7f98c1ed4 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/gatewayclass.yaml @@ -0,0 +1,14 @@ +{{- if and (.Values.gatewayClass).enabled (.Values.providers.kubernetesGateway).enabled }} +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: GatewayClass +metadata: + name: {{ default "traefik" .Values.gatewayClass.name }} + labels: + {{- include "traefik.labels" . | nindent 4 }} + {{- with .Values.gatewayClass.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + controllerName: traefik.io/gateway-controller +{{- end }} diff --git a/charts/traefik/traefik/30.0.2/templates/hpa.yaml b/charts/traefik/traefik/30.0.2/templates/hpa.yaml new file mode 100644 index 000000000..cfa1e5a49 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/hpa.yaml @@ -0,0 +1,35 @@ +{{- if .Values.autoscaling.enabled }} + +{{- if not .Values.autoscaling.maxReplicas }} + {{- fail "ERROR: maxReplicas is required on HPA" }} +{{- end }} + +{{- if semverCompare ">=1.23.0-0" .Capabilities.KubeVersion.Version }} +apiVersion: autoscaling/v2 +{{- else }} +apiVersion: autoscaling/v2beta2 +{{- end }} +kind: HorizontalPodAutoscaler +metadata: + name: {{ template "traefik.fullname" . }} + namespace: {{ template "traefik.namespace" . }} + labels: + {{- include "traefik.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ template "traefik.fullname" . }} +{{- if .Values.autoscaling.minReplicas }} + minReplicas: {{ .Values.autoscaling.minReplicas }} +{{- end }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} +{{- if .Values.autoscaling.metrics }} + metrics: +{{ toYaml .Values.autoscaling.metrics | indent 4 }} +{{- end }} +{{- if .Values.autoscaling.behavior }} + behavior: +{{ toYaml .Values.autoscaling.behavior | indent 4 }} +{{- end }} +{{- end }} diff --git a/charts/traefik/traefik/30.0.2/templates/hub-admission-controller.yaml b/charts/traefik/traefik/30.0.2/templates/hub-admission-controller.yaml new file mode 100644 index 000000000..6a6451600 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/hub-admission-controller.yaml @@ -0,0 +1,250 @@ +{{- if .Values.hub.token -}} +{{- if .Values.hub.apimanagement.enabled }} +{{- $cert := include "traefik-hub.webhook_cert" . | fromYaml }} +--- +apiVersion: v1 +kind: Secret +type: kubernetes.io/tls +metadata: + name: hub-agent-cert + namespace: {{ template "traefik.namespace" . }} + labels: + {{- include "traefik.labels" . | nindent 4 }} +data: + tls.crt: {{ $cert.Cert }} + tls.key: {{ $cert.Key }} + +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: hub-edge-ingress + labels: + {{- include "traefik.labels" . | nindent 4 }} +webhooks: + - name: admission.traefik.svc + clientConfig: + service: + name: admission + namespace: {{ template "traefik.namespace" . }} + path: /edge-ingress + caBundle: {{ $cert.Cert }} + sideEffects: None + admissionReviewVersions: + - v1 + rules: + - operations: + - CREATE + - UPDATE + - DELETE + apiGroups: + - hub.traefik.io + apiVersions: + - v1alpha1 + resources: + - edgeingresses + scope: Namespaced + +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: hub-acp + labels: + {{- include "traefik.labels" . | nindent 4 }} +webhooks: + - name: admission.traefik.svc + clientConfig: + service: + name: admission + namespace: {{ template "traefik.namespace" . }} + path: /acp + caBundle: {{ $cert.Cert }} + sideEffects: None + admissionReviewVersions: + - v1 + rules: + - operations: + - CREATE + - UPDATE + - DELETE + apiGroups: + - hub.traefik.io + apiVersions: + - v1alpha1 + resources: + - accesscontrolpolicies + +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: hub-api + labels: + {{- include "traefik.labels" . | nindent 4 }} +webhooks: + - name: hub-agent.traefik.portal + clientConfig: + service: + name: admission + namespace: {{ template "traefik.namespace" . }} + path: /api-portal + caBundle: {{ $cert.Cert }} + sideEffects: None + admissionReviewVersions: + - v1 + rules: + - operations: + - CREATE + - UPDATE + - DELETE + apiGroups: + - hub.traefik.io + apiVersions: + - v1alpha1 + resources: + - apiportals + - name: hub-agent.traefik.gateway + clientConfig: + service: + name: admission + namespace: {{ template "traefik.namespace" . }} + path: /api-gateway + caBundle: {{ $cert.Cert }} + sideEffects: None + admissionReviewVersions: + - v1 + rules: + - operations: + - CREATE + - UPDATE + - DELETE + apiGroups: + - hub.traefik.io + apiVersions: + - v1alpha1 + resources: + - apigateways + - name: hub-agent.traefik.api + clientConfig: + service: + name: admission + namespace: {{ template "traefik.namespace" . }} + path: /api + caBundle: {{ $cert.Cert }} + sideEffects: None + admissionReviewVersions: + - v1 + rules: + - operations: + - CREATE + - UPDATE + - DELETE + apiGroups: + - hub.traefik.io + apiVersions: + - v1alpha1 + resources: + - apis + - name: hub-agent.traefik.collection + clientConfig: + service: + name: admission + namespace: {{ template "traefik.namespace" . }} + path: /api-collection + caBundle: {{ $cert.Cert }} + sideEffects: None + admissionReviewVersions: + - v1 + rules: + - operations: + - CREATE + - UPDATE + - DELETE + apiGroups: + - hub.traefik.io + apiVersions: + - v1alpha1 + resources: + - apicollections + - name: hub-agent.traefik.access + clientConfig: + service: + name: admission + namespace: {{ template "traefik.namespace" . }} + path: /api-access + caBundle: {{ $cert.Cert }} + sideEffects: None + admissionReviewVersions: + - v1 + rules: + - operations: + - CREATE + - UPDATE + - DELETE + apiGroups: + - hub.traefik.io + apiVersions: + - v1alpha1 + resources: + - apiaccesses + - name: hub-agent.traefik.rate-limit + clientConfig: + service: + name: admission + namespace: {{ template "traefik.namespace" . }} + path: /api-rate-limit + caBundle: {{ $cert.Cert }} + sideEffects: None + admissionReviewVersions: + - v1 + rules: + - operations: + - CREATE + - UPDATE + - DELETE + apiGroups: + - hub.traefik.io + apiVersions: + - v1alpha1 + resources: + - apiratelimits + - name: hub-agent.traefik.version + clientConfig: + service: + name: admission + namespace: {{ template "traefik.namespace" . }} + path: /api-version + caBundle: {{ $cert.Cert }} + sideEffects: None + admissionReviewVersions: + - v1 + rules: + - operations: + - CREATE + - UPDATE + - DELETE + apiGroups: + - hub.traefik.io + apiVersions: + - v1alpha1 + resources: + - apiversions + +--- +apiVersion: v1 +kind: Service +metadata: + name: admission + namespace: {{ template "traefik.namespace" . }} + labels: + {{- include "traefik.labels" . | nindent 4 }} +spec: + ports: + - name: https + port: 443 + targetPort: admission + selector: + {{- include "traefik.labelselector" . | nindent 4 }} +{{- end -}} +{{- end -}} diff --git a/charts/traefik/traefik/30.0.2/templates/hub-apiportal.yaml b/charts/traefik/traefik/30.0.2/templates/hub-apiportal.yaml new file mode 100644 index 000000000..246b127ed --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/hub-apiportal.yaml @@ -0,0 +1,19 @@ +{{- if .Values.hub.apimanagement.enabled }} +--- +apiVersion: v1 +kind: Service +metadata: + name: apiportal + namespace: {{ template "traefik.namespace" . }} + labels: + {{- include "traefik.labels" . | nindent 4 }} +spec: + ports: + - name: apiportal + port: 9903 + protocol: TCP + targetPort: apiportal + selector: + {{- include "traefik.labelselector" . | nindent 4 }} +{{- end -}} + diff --git a/charts/traefik/traefik/30.0.2/templates/ingressclass.yaml b/charts/traefik/traefik/30.0.2/templates/ingressclass.yaml new file mode 100644 index 000000000..6a8ff8199 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/ingressclass.yaml @@ -0,0 +1,12 @@ +{{- if .Values.ingressClass.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + annotations: + ingressclass.kubernetes.io/is-default-class: {{ .Values.ingressClass.isDefaultClass | quote }} + labels: + {{- include "traefik.labels" . | nindent 4 }} + name: {{ .Values.ingressClass.name | default (include "traefik.fullname" .) }} +spec: + controller: traefik.io/ingress-controller +{{- end -}} diff --git a/charts/traefik/traefik/30.0.2/templates/ingressroute.yaml b/charts/traefik/traefik/30.0.2/templates/ingressroute.yaml new file mode 100644 index 000000000..2f35abb2a --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/ingressroute.yaml @@ -0,0 +1,43 @@ +{{ range $name, $config := .Values.ingressRoute }} +{{ if $config.enabled }} +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: {{ $.Release.Name }}-{{ $name }} + namespace: {{ template "traefik.namespace" $ }} + annotations: + {{- if and $.Values.ingressClass.enabled $.Values.providers.kubernetesCRD.enabled $.Values.providers.kubernetesCRD.ingressClass }} + kubernetes.io/ingress.class: {{ $.Values.providers.kubernetesCRD.ingressClass }} + {{- end }} + {{- with $config.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "traefik.labels" $ | nindent 4 }} + {{- with $config.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + entryPoints: + {{- range $config.entryPoints }} + - {{ . }} + {{- end }} + routes: + - match: {{ $config.matchRule }} + kind: Rule + {{- with $config.services }} + services: + {{- toYaml . | nindent 6 }} + {{- end -}} + {{- with $config.middlewares }} + middlewares: + {{- toYaml . | nindent 6 }} + {{- end -}} + + {{- with $config.tls }} + tls: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end -}} +{{ end }} diff --git a/charts/traefik/traefik/30.0.2/templates/poddisruptionbudget.yaml b/charts/traefik/traefik/30.0.2/templates/poddisruptionbudget.yaml new file mode 100644 index 000000000..f1716397c --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/poddisruptionbudget.yaml @@ -0,0 +1,23 @@ +{{- if .Values.podDisruptionBudget.enabled -}} +{{- if .Capabilities.APIVersions.Has "policy/v1/PodDisruptionBudget" }} +apiVersion: policy/v1 +{{- else }} +apiVersion: policy/v1beta1 +{{- end }} +kind: PodDisruptionBudget +metadata: + name: {{ template "traefik.fullname" . }} + namespace: {{ template "traefik.namespace" . }} + labels: + {{- include "traefik.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "traefik.labelselector" . | nindent 6 }} + {{- if .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- end }} + {{- if .Values.podDisruptionBudget.maxUnavailable }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- end }} +{{- end -}} diff --git a/charts/traefik/traefik/30.0.2/templates/prometheusrules.yaml b/charts/traefik/traefik/30.0.2/templates/prometheusrules.yaml new file mode 100644 index 000000000..3231aba6c --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/prometheusrules.yaml @@ -0,0 +1,28 @@ +{{- if .Values.metrics.prometheus }} +{{- if (.Values.metrics.prometheus.prometheusRule).enabled }} + {{- if (not (.Capabilities.APIVersions.Has "monitoring.coreos.com/v1")) }} + {{- if (not (.Values.metrics.prometheus.disableAPICheck)) }} + {{- fail "ERROR: You have to deploy monitoring.coreos.com/v1 first" }} + {{- end }} + {{- end }} +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ template "traefik.fullname" . }} + namespace: {{ .Values.metrics.prometheus.prometheusRule.namespace | default (include "traefik.namespace" .) }} + labels: + {{- include "traefik.labels" . | nindent 4 }} + {{- with .Values.metrics.prometheus.prometheusRule.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.metrics.prometheus.prometheusRule.rules }} + groups: + - name: {{ template "traefik.name" $ }} + rules: + {{- with .Values.metrics.prometheus.prometheusRule.rules }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/traefik/traefik/30.0.2/templates/provider-file-cm.yaml b/charts/traefik/traefik/30.0.2/templates/provider-file-cm.yaml new file mode 100644 index 000000000..139a5a6ab --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/provider-file-cm.yaml @@ -0,0 +1,12 @@ +{{- if .Values.providers.file.enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "traefik.fullname" . }}-file-provider + namespace: {{ template "traefik.namespace" . }} + labels: + {{- include "traefik.labels" . | nindent 4 }} +data: + config.yml: + {{ toYaml .Values.providers.file.content | nindent 4 }} +{{- end -}} diff --git a/charts/traefik/traefik/30.0.2/templates/pvc.yaml b/charts/traefik/traefik/30.0.2/templates/pvc.yaml new file mode 100644 index 000000000..7ab96f960 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/pvc.yaml @@ -0,0 +1,26 @@ +{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) -}} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ template "traefik.fullname" . }} + namespace: {{ template "traefik.namespace" . }} + annotations: + {{- with .Values.persistence.annotations }} + {{ toYaml . | nindent 4 }} + {{- end }} + helm.sh/resource-policy: keep + labels: + {{- include "traefik.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.persistence.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} + {{- if .Values.persistence.storageClass }} + storageClassName: {{ .Values.persistence.storageClass | quote }} + {{- end }} + {{- if .Values.persistence.volumeName }} + volumeName: {{ .Values.persistence.volumeName | quote }} + {{- end }} +{{- end -}} diff --git a/charts/traefik/traefik/30.0.2/templates/rbac/clusterrole.yaml b/charts/traefik/traefik/30.0.2/templates/rbac/clusterrole.yaml new file mode 100644 index 000000000..3ab9c698e --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/rbac/clusterrole.yaml @@ -0,0 +1,286 @@ +{{- $version := include "imageVersion" $ }} +{{- if .Values.rbac.enabled }} +{{- if or + (semverCompare ">=v3.1.0-0" $version) + (.Values.providers.kubernetesGateway.enabled) + (not .Values.rbac.namespaced) + (and .Values.rbac.namespaced .Values.providers.kubernetesIngress.enabled (not .Values.providers.kubernetesIngress.disableIngressClassLookup)) +}} +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ template "traefik.clusterRoleName" . }} + labels: + {{- include "traefik.labels" . | nindent 4 }} + {{- range .Values.rbac.aggregateTo }} + rbac.authorization.k8s.io/aggregate-to-{{ . }}: "true" + {{- end }} +rules: + {{- if semverCompare ">=v3.1.0-0" $version }} + - apiGroups: + - "" + resources: + - nodes + verbs: + - get + - list + - watch + {{- end }} + - apiGroups: + - extensions + - networking.k8s.io + resources: + - ingressclasses + {{- if not .Values.rbac.namespaced }} + - ingresses + {{- end }} + verbs: + - get + - list + - watch + {{- if (.Values.providers.kubernetesGateway).enabled }} + - apiGroups: + - "" + resources: + - namespaces + verbs: + - list + - watch + - apiGroups: + - gateway.networking.k8s.io + resources: + - gatewayclasses + verbs: + - get + - list + - watch + - apiGroups: + - gateway.networking.k8s.io + resources: + - gatewayclasses/status + verbs: + - update + {{- end }} + {{- if not .Values.rbac.namespaced }} + {{- if (semverCompare "=v3.1.0-0" $version) }} + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + {{- end }} + - apiGroups: + - gateway.networking.k8s.io + resources: + - gateways + - httproutes + - referencegrants + - tcproutes + - tlsroutes + verbs: + - get + - list + - watch + - apiGroups: + - gateway.networking.k8s.io + resources: + - gateways/status + - httproutes/status + - tcproutes/status + - tlsroutes/status + verbs: + - update + {{- end }} + {{- if .Values.hub.token }} + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + {{- end }} +{{- /* not .Values.rbac.namespace */}} +{{- end }} +{{- if .Values.hub.token }} + {{- if or (semverCompare ">=v3.1.0-0" $version) .Values.hub.apimanagement.enabled }} + - apiGroups: + - "" + resources: + - endpoints + verbs: + - list + - watch + {{- end }} + - apiGroups: + - "" + resources: + - namespaces + {{- if .Values.hub.apimanagement.enabled }} + - pods + {{- end }} + verbs: + - get + - list + {{- if .Values.hub.apimanagement.enabled }} + - watch + {{- end }} + {{- if .Values.hub.apimanagement.enabled }} + - apiGroups: + - hub.traefik.io + resources: + - accesscontrolpolicies + - apiaccesses + - apiportals + - apiratelimits + - apis + - apiversions + verbs: + - list + - watch + - create + - update + - patch + - delete + - get + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - apiGroups: + - apps + resources: + - replicasets + verbs: + - get + - list + - watch + {{- if (semverCompare "=v3.1.0-0" $version) + (not .Values.rbac.namespaced) + (and .Values.rbac.namespaced .Values.providers.kubernetesIngress.enabled (not .Values.providers.kubernetesIngress.disableIngressClassLookup)) +}} +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ template "traefik.clusterRoleName" . }} + labels: + {{- include "traefik.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "traefik.clusterRoleName" . }} +subjects: + - kind: ServiceAccount + name: {{ include "traefik.serviceAccountName" . }} + namespace: {{ template "traefik.namespace" . }} +{{- end -}} +{{- end -}} diff --git a/charts/traefik/traefik/30.0.2/templates/rbac/podsecuritypolicy.yaml b/charts/traefik/traefik/30.0.2/templates/rbac/podsecuritypolicy.yaml new file mode 100644 index 000000000..bc0a3bdc7 --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/rbac/podsecuritypolicy.yaml @@ -0,0 +1,68 @@ +{{- if .Values.podSecurityPolicy.enabled }} +{{- if semverCompare ">=1.25.0-0" .Capabilities.KubeVersion.Version }} + {{- fail "ERROR: PodSecurityPolicy has been removed in Kubernetes v1.25+" }} +{{- end }} +--- +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + annotations: + seccomp.security.alpha.kubernetes.io/allowedProfileNames: runtime/default + seccomp.security.alpha.kubernetes.io/defaultProfileName: runtime/default + name: {{ template "traefik.fullname" . }} + labels: + {{- include "traefik.labels" . | nindent 4 }} +spec: + privileged: false + allowPrivilegeEscalation: false + requiredDropCapabilities: + - ALL +{{- if not .Values.securityContext.runAsNonRoot }} + allowedCapabilities: + - NET_BIND_SERVICE +{{- end }} + hostNetwork: {{ .Values.hostNetwork }} + hostIPC: false + hostPID: false + fsGroup: +{{- if .Values.securityContext.runAsNonRoot }} + ranges: + - max: 65535 + min: 1 + rule: MustRunAs +{{- else }} + rule: RunAsAny +{{- end }} +{{- if .Values.hostNetwork }} + hostPorts: + - max: 65535 + min: 1 +{{- end }} + readOnlyRootFilesystem: true + runAsUser: +{{- if .Values.securityContext.runAsNonRoot }} + rule: MustRunAsNonRoot +{{- else }} + rule: RunAsAny +{{- end }} + seLinux: + rule: RunAsAny + supplementalGroups: +{{- if .Values.securityContext.runAsNonRoot }} + ranges: + - max: 65535 + min: 1 + rule: MustRunAs +{{- else }} + rule: RunAsAny +{{- end }} + volumes: + - configMap + - downwardAPI + - secret + - emptyDir + - projected +{{- if .Values.persistence.enabled }} + - persistentVolumeClaim +{{- end -}} +{{- end -}} diff --git a/charts/traefik/traefik/30.0.2/templates/rbac/role.yaml b/charts/traefik/traefik/30.0.2/templates/rbac/role.yaml new file mode 100644 index 000000000..8cd9837cf --- /dev/null +++ b/charts/traefik/traefik/30.0.2/templates/rbac/role.yaml @@ -0,0 +1,168 @@ +{{- $version := include "imageVersion" $ }} +{{- $ingressNamespaces := concat (include "traefik.namespace" . | list) .Values.providers.kubernetesIngress.namespaces -}} +{{- $CRDNamespaces := concat (include "traefik.namespace" . | list) .Values.providers.kubernetesCRD.namespaces -}} +{{- $gatewayNamespaces := concat (include "traefik.namespace" . | list) ((.Values.providers.kubernetesGateway).namespaces) -}} +{{- $allNamespaces := sortAlpha (uniq (concat $ingressNamespaces $CRDNamespaces $gatewayNamespaces)) -}} + +{{- if and .Values.rbac.enabled .Values.rbac.namespaced -}} +{{- range $allNamespaces }} +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ template "traefik.fullname" $ }} + namespace: {{ . }} + labels: + {{- include "traefik.labels" $ | nindent 4 }} +rules: + {{- if (semverCompare "://:. Default: http://localhost:4318/v1/metrics + endpoint: + # -- Additional headers sent with metrics by the reporter to the OpenTelemetry Collector. + headers: + ## Defines the TLS configuration used by the reporter to send metrics to the OpenTelemetry Collector. + tls: + # -- The path to the certificate authority, it defaults to the system bundle. + ca: + # -- The path to the public certificate. When using this option, setting the key option is required. + cert: + # -- The path to the private key. When using this option, setting the cert option is required. + key: + # -- When set to true, the TLS connection accepts any certificate presented by the server regardless of the hostnames it covers. + insecureSkipVerify: + grpc: + # -- Set to true in order to send metrics to the OpenTelemetry Collector using gRPC + enabled: false + # -- Format: ://:. Default: http://localhost:4318/v1/metrics + endpoint: + # -- Allows reporter to send metrics to the OpenTelemetry Collector without using a secured protocol. + insecure: + ## Defines the TLS configuration used by the reporter to send metrics to the OpenTelemetry Collector. + tls: + # -- The path to the certificate authority, it defaults to the system bundle. + ca: + # -- The path to the public certificate. When using this option, setting the key option is required. + cert: + # -- The path to the private key. When using this option, setting the cert option is required. + key: + # -- When set to true, the TLS connection accepts any certificate presented by the server regardless of the hostnames it covers. + insecureSkipVerify: + +## Tracing +# -- https://doc.traefik.io/traefik/observability/tracing/overview/ +tracing: + # -- Enables tracing for internal resources. Default: false. + addInternals: + otlp: + # -- See https://doc.traefik.io/traefik/v3.0/observability/tracing/opentelemetry/ + enabled: false + http: + # -- Set to true in order to send metrics to the OpenTelemetry Collector using HTTP. + enabled: false + # -- Format: ://:. Default: http://localhost:4318/v1/metrics + endpoint: + # -- Additional headers sent with metrics by the reporter to the OpenTelemetry Collector. + headers: + ## Defines the TLS configuration used by the reporter to send metrics to the OpenTelemetry Collector. + tls: + # -- The path to the certificate authority, it defaults to the system bundle. + ca: + # -- The path to the public certificate. When using this option, setting the key option is required. + cert: + # -- The path to the private key. When using this option, setting the cert option is required. + key: + # -- When set to true, the TLS connection accepts any certificate presented by the server regardless of the hostnames it covers. + insecureSkipVerify: + grpc: + # -- Set to true in order to send metrics to the OpenTelemetry Collector using gRPC + enabled: false + # -- Format: ://:. Default: http://localhost:4318/v1/metrics + endpoint: + # -- Allows reporter to send metrics to the OpenTelemetry Collector without using a secured protocol. + insecure: + ## Defines the TLS configuration used by the reporter to send metrics to the OpenTelemetry Collector. + tls: + # -- The path to the certificate authority, it defaults to the system bundle. + ca: + # -- The path to the public certificate. When using this option, setting the key option is required. + cert: + # -- The path to the private key. When using this option, setting the cert option is required. + key: + # -- When set to true, the TLS connection accepts any certificate presented by the server regardless of the hostnames it covers. + insecureSkipVerify: + +# -- Global command arguments to be passed to all traefik's pods +globalArguments: +- "--global.checknewversion" +- "--global.sendanonymoususage" + +# -- Additional arguments to be passed at Traefik's binary +# See [CLI Reference](https://docs.traefik.io/reference/static-configuration/cli/) +# Use curly braces to pass values: `helm install --set="additionalArguments={--providers.kubernetesingress.ingressclass=traefik-internal,--log.level=DEBUG}"` +additionalArguments: [] +# - "--providers.kubernetesingress.ingressclass=traefik-internal" +# - "--log.level=DEBUG" + +# -- Environment variables to be passed to Traefik's binary +# @default -- See _values.yaml_ +env: +- name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name +- name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + +# -- Environment variables to be passed to Traefik's binary from configMaps or secrets +envFrom: [] + +ports: + traefik: + port: 9000 + # -- Use hostPort if set. + # hostPort: 9000 + # + # -- Use hostIP if set. If not set, Kubernetes will default to 0.0.0.0, which + # means it's listening on all your interfaces and all your IPs. You may want + # to set this value if you need traefik to listen on specific interface + # only. + # hostIP: 192.168.100.10 + + # Defines whether the port is exposed if service.type is LoadBalancer or + # NodePort. + # + # -- You SHOULD NOT expose the traefik port on production deployments. + # If you want to access it from outside your cluster, + # use `kubectl port-forward` or create a secure ingress + expose: + default: false + # -- The exposed port for this service + exposedPort: 9000 + # -- The port protocol (TCP/UDP) + protocol: TCP + web: + ## -- Enable this entrypoint as a default entrypoint. When a service doesn't explicitly set an entrypoint it will only use this entrypoint. + # asDefault: true + port: 8000 + # hostPort: 8000 + # containerPort: 8000 + expose: + default: true + exposedPort: 80 + ## -- Different target traefik port on the cluster, useful for IP type LB + # targetPort: 80 + # The port protocol (TCP/UDP) + protocol: TCP + # -- Use nodeport if set. This is useful if you have configured Traefik in a + # LoadBalancer. + # nodePort: 32080 + # Port Redirections + # Added in 2.2, you can make permanent redirects via entrypoints. + # https://docs.traefik.io/routing/entrypoints/#redirection + # redirectTo: + # port: websecure + # (Optional) + # priority: 10 + # permanent: true + # + # -- Trust forwarded headers information (X-Forwarded-*). + # forwardedHeaders: + # trustedIPs: [] + # insecure: false + # + # -- Enable the Proxy Protocol header parsing for the entry point + # proxyProtocol: + # trustedIPs: [] + # insecure: false + # + # -- Set transport settings for the entrypoint; see also + # https://doc.traefik.io/traefik/routing/entrypoints/#transport + transport: + respondingTimeouts: + readTimeout: + writeTimeout: + idleTimeout: + lifeCycle: + requestAcceptGraceTimeout: + graceTimeOut: + keepAliveMaxRequests: + keepAliveMaxTime: + websecure: + ## -- Enable this entrypoint as a default entrypoint. When a service doesn't explicitly set an entrypoint it will only use this entrypoint. + # asDefault: true + port: 8443 + # hostPort: 8443 + # containerPort: 8443 + expose: + default: true + exposedPort: 443 + ## -- Different target traefik port on the cluster, useful for IP type LB + # targetPort: 80 + ## -- The port protocol (TCP/UDP) + protocol: TCP + # nodePort: 32443 + ## -- Specify an application protocol. This may be used as a hint for a Layer 7 load balancer. + # appProtocol: https + # + ## -- Enable HTTP/3 on the entrypoint + ## Enabling it will also enable http3 experimental feature + ## https://doc.traefik.io/traefik/routing/entrypoints/#http3 + ## There are known limitations when trying to listen on same ports for + ## TCP & UDP (Http3). There is a workaround in this chart using dual Service. + ## https://github.com/kubernetes/kubernetes/issues/47249#issuecomment-587960741 + http3: + enabled: false + # advertisedPort: 4443 + # + # -- Trust forwarded headers information (X-Forwarded-*). + # forwardedHeaders: + # trustedIPs: [] + # insecure: false + # + # -- Enable the Proxy Protocol header parsing for the entry point + # proxyProtocol: + # trustedIPs: [] + # insecure: false + # + # -- Set transport settings for the entrypoint; see also + # https://doc.traefik.io/traefik/routing/entrypoints/#transport + transport: + respondingTimeouts: + readTimeout: + writeTimeout: + idleTimeout: + lifeCycle: + requestAcceptGraceTimeout: + graceTimeOut: + keepAliveMaxRequests: + keepAliveMaxTime: + # + ## Set TLS at the entrypoint + ## https://doc.traefik.io/traefik/routing/entrypoints/#tls + tls: + enabled: true + # this is the name of a TLSOption definition + options: "" + certResolver: "" + domains: [] + # - main: example.com + # sans: + # - foo.example.com + # - bar.example.com + # + # -- One can apply Middlewares on an entrypoint + # https://doc.traefik.io/traefik/middlewares/overview/ + # https://doc.traefik.io/traefik/routing/entrypoints/#middlewares + # -- /!\ It introduces here a link between your static configuration and your dynamic configuration /!\ + # It follows the provider naming convention: https://doc.traefik.io/traefik/providers/overview/#provider-namespace + # middlewares: + # - namespace-name1@kubernetescrd + # - namespace-name2@kubernetescrd + middlewares: [] + metrics: + # -- When using hostNetwork, use another port to avoid conflict with node exporter: + # https://github.com/prometheus/prometheus/wiki/Default-port-allocations + port: 9100 + # hostPort: 9100 + # Defines whether the port is exposed if service.type is LoadBalancer or + # NodePort. + # + # -- You may not want to expose the metrics port on production deployments. + # If you want to access it from outside your cluster, + # use `kubectl port-forward` or create a secure ingress + expose: + default: false + # -- The exposed port for this service + exposedPort: 9100 + # -- The port protocol (TCP/UDP) + protocol: TCP + +# -- TLS Options are created as [TLSOption CRDs](https://doc.traefik.io/traefik/https/tls/#tls-options) +# When using `labelSelector`, you'll need to set labels on tlsOption accordingly. +# See EXAMPLE.md for details. +tlsOptions: {} + +# -- TLS Store are created as [TLSStore CRDs](https://doc.traefik.io/traefik/https/tls/#default-certificate). This is useful if you want to set a default certificate. See EXAMPLE.md for details. +tlsStore: {} + +service: + enabled: true + ## -- Single service is using `MixedProtocolLBService` feature gate. + ## -- When set to false, it will create two Service, one for TCP and one for UDP. + single: true + type: LoadBalancer + # -- Additional annotations applied to both TCP and UDP services (e.g. for cloud provider specific config) + annotations: {} + # -- Additional annotations for TCP service only + annotationsTCP: {} + # -- Additional annotations for UDP service only + annotationsUDP: {} + # -- Additional service labels (e.g. for filtering Service by custom labels) + labels: {} + # -- Additional entries here will be added to the service spec. + # -- Cannot contain type, selector or ports entries. + spec: {} + # externalTrafficPolicy: Cluster + # loadBalancerIP: "1.2.3.4" + # clusterIP: "2.3.4.5" + loadBalancerSourceRanges: [] + # - 192.168.0.1/32 + # - 172.16.0.0/16 + ## -- Class of the load balancer implementation + # loadBalancerClass: service.k8s.aws/nlb + externalIPs: [] + # - 1.2.3.4 + ## One of SingleStack, PreferDualStack, or RequireDualStack. + # ipFamilyPolicy: SingleStack + ## List of IP families (e.g. IPv4 and/or IPv6). + ## ref: https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services + # ipFamilies: + # - IPv4 + # - IPv6 + ## + additionalServices: {} + ## -- An additional and optional internal Service. + ## Same parameters as external Service + # internal: + # type: ClusterIP + # # labels: {} + # # annotations: {} + # # spec: {} + # # loadBalancerSourceRanges: [] + # # externalIPs: [] + # # ipFamilies: [ "IPv4","IPv6" ] + +autoscaling: + # -- Create HorizontalPodAutoscaler object. + # See EXAMPLES.md for more details. + enabled: false + +persistence: + # -- Enable persistence using Persistent Volume Claims + # ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + # It can be used to store TLS certificates, see `storage` in certResolvers + enabled: false + name: data + # existingClaim: "" + accessMode: ReadWriteOnce + size: 128Mi + # storageClass: "" + # volumeName: "" + path: /data + annotations: {} + # -- Only mount a subpath of the Volume into the pod + # subPath: "" + +# -- Certificates resolvers configuration. +# Ref: https://doc.traefik.io/traefik/https/acme/#certificate-resolvers +# See EXAMPLES.md for more details. +certResolvers: {} + +# -- If hostNetwork is true, runs traefik in the host network namespace +# To prevent unschedulabel pods due to port collisions, if hostNetwork=true +# and replicas>1, a pod anti-affinity is recommended and will be set if the +# affinity is left as default. +hostNetwork: false + +# -- Whether Role Based Access Control objects like roles and rolebindings should be created +rbac: + enabled: true + # If set to false, installs ClusterRole and ClusterRoleBinding so Traefik can be used across namespaces. + # If set to true, installs Role and RoleBinding instead of ClusterRole/ClusterRoleBinding. Providers will only watch target namespace. + # When combined with providers.kubernetesIngress.disableIngressClassLookup: true and Traefik V3, ClusterRole to watch IngressClass is also disabled. + namespaced: false + # Enable user-facing roles + # https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles + # aggregateTo: [ "admin" ] + # List of Kubernetes secrets that are accessible for Traefik. If empty, then access is granted to every secret. + secretResourceNames: [] + +# -- Enable to create a PodSecurityPolicy and assign it to the Service Account via RoleBinding or ClusterRoleBinding +podSecurityPolicy: + enabled: false + +# -- The service account the pods will use to interact with the Kubernetes API +serviceAccount: + # If set, an existing service account is used + # If not set, a service account is created automatically using the fullname template + name: "" + +# -- Additional serviceAccount annotations (e.g. for oidc authentication) +serviceAccountAnnotations: {} + +# -- [Resources](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for `traefik` container. +resources: {} + +# -- This example pod anti-affinity forces the scheduler to put traefik pods +# -- on nodes where no other traefik pods are scheduled. +# It should be used when hostNetwork: true to prevent port conflicts +affinity: {} +# podAntiAffinity: +# requiredDuringSchedulingIgnoredDuringExecution: +# - labelSelector: +# matchLabels: +# app.kubernetes.io/name: '{{ template "traefik.name" . }}' +# app.kubernetes.io/instance: '{{ .Release.Name }}-{{ .Release.Namespace }}' +# topologyKey: kubernetes.io/hostname + +# -- nodeSelector is the simplest recommended form of node selection constraint. +nodeSelector: {} +# -- Tolerations allow the scheduler to schedule pods with matching taints. +tolerations: [] +# -- You can use topology spread constraints to control +# how Pods are spread across your cluster among failure-domains. +topologySpreadConstraints: [] +# This example topologySpreadConstraints forces the scheduler to put traefik pods +# on nodes where no other traefik pods are scheduled. +# - labelSelector: +# matchLabels: +# app: '{{ template "traefik.name" . }}' +# maxSkew: 1 +# topologyKey: kubernetes.io/hostname +# whenUnsatisfiable: DoNotSchedule + +# -- [Pod Priority and Preemption](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/) +priorityClassName: "" + +# -- [SecurityContext](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#security-context-1) +# @default -- See _values.yaml_ +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: [ALL] + readOnlyRootFilesystem: true + +# -- [Pod Security Context](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#security-context) +# @default -- See _values.yaml_ +podSecurityContext: + runAsGroup: 65532 + runAsNonRoot: true + runAsUser: 65532 + +# +# -- Extra objects to deploy (value evaluated as a template) +# +# In some cases, it can avoid the need for additional, extended or adhoc deployments. +# See #595 for more details and traefik/tests/values/extra.yaml for example. +extraObjects: [] + +# -- This field override the default Release Namespace for Helm. +# It will not affect optional CRDs such as `ServiceMonitor` and `PrometheusRules` +namespaceOverride: + +## -- This field override the default app.kubernetes.io/instance label for all Objects. +instanceLabelOverride: + +# Traefik Hub configuration. See https://doc.traefik.io/traefik-hub/ +hub: + # -- Name of `Secret` with key 'token' set to a valid license token. + # It enables API Gateway. + token: + apimanagement: + # -- Set to true in order to enable API Management. Requires a valid license token. + enabled: + admission: + # -- WebHook admission server listen address. Default: "0.0.0.0:9943". + listenAddr: + # -- Certificate of the WebHook admission server. Default: "hub-agent-cert". + secretName: + + ratelimit: + redis: + # -- Enable Redis Cluster. Default: true. + cluster: + # -- Database used to store information. Default: "0". + database: + # -- Endpoints of the Redis instances to connect to. Default: "". + endpoints: + # -- The username to use when connecting to Redis endpoints. Default: "". + username: + # -- The password to use when connecting to Redis endpoints. Default: "". + password: + sentinel: + # -- Name of the set of main nodes to use for main selection. Required when using Sentinel. Default: "". + masterset: + # -- Username to use for sentinel authentication (can be different from endpoint username). Default: "". + username: + # -- Password to use for sentinel authentication (can be different from endpoint password). Default: "". + password: + # -- Timeout applied on connection with redis. Default: "0s". + timeout: + tls: + # -- Path to the certificate authority used for the secured connection. + ca: + # -- Path to the public certificate used for the secure connection. + cert: + # -- Path to the private key used for the secure connection. + key: + # -- When insecureSkipVerify is set to true, the TLS connection accepts any certificate presented by the server. Default: false. + insecureSkipVerify: + # Enable export of errors logs to the platform. Default: true. + sendlogs: diff --git a/index.yaml b/index.yaml index b2f7c222e..b092edecb 100644 --- a/index.yaml +++ b/index.yaml @@ -5321,6 +5321,33 @@ entries: - assets/mongodb/community-operator-0.8.1.tgz version: 0.8.1 confluent-for-kubernetes: + - annotations: + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: Confluent for Kubernetes + catalog.cattle.io/kube-version: '>=1.15-0' + catalog.cattle.io/release-name: confluent-for-kubernetes + apiVersion: v1 + appVersion: 2.9.0 + created: "2024-07-31T00:38:57.531748307Z" + description: A Helm chart to deploy Confluent for Kubernetes + digest: cabc42bdd112c54690329fd7486e825d749f51086e86fd7730ab96258ddd66be + home: https://www.confluent.io/ + icon: file://assets/icons/confluent-for-kubernetes.png + keywords: + - Confluent + - Confluent Operator + - Confluent Platform + - CFK + kubeVersion: '>=1.15-0' + maintainers: + - email: operator@confluent.io + name: Confluent Operator + name: confluent-for-kubernetes + sources: + - https://docs.confluent.io/current/index.html + urls: + - assets/confluent/confluent-for-kubernetes-0.1033.3.tgz + version: 0.1033.3 - annotations: catalog.cattle.io/certified: partner catalog.cattle.io/display-name: Confluent for Kubernetes @@ -6258,10 +6285,27 @@ entries: catalog.cattle.io/featured: "1" catalog.cattle.io/release-name: cost-analyzer apiVersion: v2 + appVersion: 2.3.4 + created: "2024-07-31T00:38:59.817727957Z" + description: Kubecost Helm chart - monitor your cloud costs! + digest: 4d2f056db72c23698e64e8858e8865a8c418b85166f710ef27757f4a14c450d0 + icon: file://assets/icons/cost-analyzer.png + name: cost-analyzer + urls: + - assets/kubecost/cost-analyzer-2.3.4.tgz + version: 2.3.4 + - annotations: + artifacthub.io/links: | + - name: Homepage + url: https://www.kubecost.com + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: Kubecost + catalog.cattle.io/release-name: cost-analyzer + apiVersion: v2 appVersion: 2.3.3 created: "2024-07-19T00:46:41.28536583Z" description: Kubecost Helm chart - monitor your cloud costs! - digest: 4611b2305b95711e576c4ccd354a46e588fe7811a242b64a001f4d1d054749bd + digest: e789ce25b767bbe8c78822bf3e8095e022d5813bf703095b28bd51f873dde723 icon: file://assets/icons/cost-analyzer.png name: cost-analyzer urls: @@ -14179,6 +14223,64 @@ entries: - assets/intel/intel-device-plugins-sgx-0.26.1.tgz version: 0.26.1 jenkins: + - annotations: + artifacthub.io/category: integration-delivery + artifacthub.io/changes: | + - Introduce capability of set skipTlsVerify and usageRestricted flags in additionalClouds + artifacthub.io/images: | + - name: jenkins + image: docker.io/jenkins/jenkins:2.452.3-jdk17 + - name: k8s-sidecar + image: docker.io/kiwigrid/k8s-sidecar:1.27.5 + - name: inbound-agent + image: jenkins/inbound-agent:3256.v88a_f6e922152-1 + artifacthub.io/license: Apache-2.0 + artifacthub.io/links: | + - name: Chart Source + url: https://github.com/jenkinsci/helm-charts/tree/main/charts/jenkins + - name: Jenkins + url: https://www.jenkins.io/ + - name: support + url: https://github.com/jenkinsci/helm-charts/issues + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: Jenkins + catalog.cattle.io/kube-version: '>=1.14-0' + catalog.cattle.io/release-name: jenkins + apiVersion: v2 + appVersion: 2.452.3 + created: "2024-07-31T00:38:58.400154727Z" + description: 'Jenkins - Build great things at any scale! As the leading open source + automation server, Jenkins provides over 1800 plugins to support building, deploying + and automating any project. ' + digest: a5147f5f053e1718e234a1128738d14c270284b6dde2faacadbed9e46fd19286 + home: https://www.jenkins.io/ + icon: file://assets/icons/jenkins.svg + keywords: + - jenkins + - ci + - devops + kubeVersion: '>=1.14-0' + maintainers: + - email: maor.friedman@redhat.com + name: maorfr + - email: mail@torstenwalter.de + name: torstenwalter + - email: garridomota@gmail.com + name: mogaal + - email: wmcdona89@gmail.com + name: wmcdona89 + - email: timjacomb1@gmail.com + name: timja + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-inbound-agent + - https://github.com/maorfr/kube-tasks + - https://github.com/jenkinsci/configuration-as-code-plugin + type: application + urls: + - assets/jenkins/jenkins-5.5.0.tgz + version: 5.5.0 - annotations: artifacthub.io/category: integration-delivery artifacthub.io/changes: | @@ -24008,6 +24110,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-07-31T00:39:00.718507122Z" + dependencies: + - condition: infrastructure.enabled,newrelic-infrastructure.enabled + name: newrelic-infrastructure + repository: file://./charts/newrelic-infrastructure + version: 3.34.3 + - condition: prometheus.enabled,nri-prometheus.enabled + name: nri-prometheus + repository: file://./charts/nri-prometheus + version: 2.1.18 + - condition: newrelic-prometheus-agent.enabled + name: newrelic-prometheus-agent + repository: file://./charts/newrelic-prometheus-agent + version: 1.14.3 + - condition: webhook.enabled,nri-metadata-injection.enabled + name: nri-metadata-injection + repository: file://./charts/nri-metadata-injection + version: 4.20.3 + - condition: metrics-adapter.enabled,newrelic-k8s-metrics-adapter.enabled + name: newrelic-k8s-metrics-adapter + repository: file://./charts/newrelic-k8s-metrics-adapter + version: 1.11.1 + - condition: ksm.enabled,kube-state-metrics.enabled + name: kube-state-metrics + repository: file://./charts/kube-state-metrics + version: 5.12.1 + - condition: kubeEvents.enabled,nri-kube-events.enabled + name: nri-kube-events + repository: file://./charts/nri-kube-events + version: 3.10.2 + - condition: logging.enabled,newrelic-logging.enabled + name: newrelic-logging + repository: file://./charts/newrelic-logging + version: 1.22.3 + - condition: newrelic-pixie.enabled + name: newrelic-pixie + repository: file://./charts/newrelic-pixie + version: 2.1.4 + - condition: k8s-agents-operator.enabled + name: k8s-agents-operator + repository: file://./charts/k8s-agents-operator + version: 0.10.0 + - alias: pixie-chart + condition: pixie-chart.enabled + name: pixie-operator-chart + repository: file://./charts/pixie-operator-chart + version: 0.1.6 + - condition: newrelic-infra-operator.enabled + name: newrelic-infra-operator + repository: file://./charts/newrelic-infra-operator + version: 2.11.1 + description: Groups together the individual charts for the New Relic Kubernetes + solution for a more comfortable deployment. + digest: 30b6a87093ca1a7f9dd9804d8f8408985d2eefbc23e9483e7710a9caee0b36c7 + 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.88.tgz + version: 5.0.88 - annotations: catalog.cattle.io/certified: partner catalog.cattle.io/display-name: New Relic @@ -31900,6 +32091,37 @@ entries: - assets/btp/sextant-2.2.21.tgz version: 2.2.21 speedscale-operator: + - annotations: + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: Speedscale Operator + catalog.cattle.io/kube-version: '>= 1.17.0-0' + catalog.cattle.io/release-name: speedscale-operator + apiVersion: v1 + appVersion: 2.2.231 + created: "2024-07-31T00:39:01.286001105Z" + description: Stress test your APIs with real world scenarios. Collect and replay + traffic without scripting. + digest: 52ef084baac8258651df5f2246736b78ee2938849063d88900cf1b16e93ef80e + 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.231.tgz + version: 2.2.231 - annotations: catalog.cattle.io/certified: partner catalog.cattle.io/display-name: Speedscale Operator @@ -35621,6 +35843,41 @@ entries: - assets/intel/tcs-issuer-0.1.0.tgz version: 0.1.0 traefik: + - annotations: + artifacthub.io/changes: "- \"fix(Traefik Hub): missing RBACs for Traefik Hub\"\n- + \"chore(release): \U0001F680 publish v30.0.2\"\n" + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: Traefik Proxy + catalog.cattle.io/kube-version: '>=1.22.0-0' + catalog.cattle.io/release-name: traefik + apiVersion: v2 + appVersion: v3.1.0 + created: "2024-07-31T00:39:01.603886555Z" + description: A Traefik based Kubernetes ingress controller + digest: 950e65cca161dde1d264dc9e2651d572a2fd723a2e04e1f56bf550da154a5f4f + home: https://traefik.io/ + icon: file://assets/icons/traefik.png + keywords: + - traefik + - ingress + - networking + kubeVersion: '>=1.22.0-0' + maintainers: + - email: michel.loiseleur@traefik.io + name: mloiseleur + - email: charlie.haley@traefik.io + name: charlie-haley + - email: remi.buisson@traefik.io + name: darkweaver87 + - name: jnoordsij + name: traefik + sources: + - https://github.com/traefik/traefik + - https://github.com/traefik/traefik-helm-chart + type: application + urls: + - assets/traefik/traefik-30.0.2.tgz + version: 30.0.2 - annotations: artifacthub.io/changes: "- \"fix: \U0001F41B ingressroute default name\"\n- \"fix: namespaced RBACs hub api gateway\"\n- \"fix: can't set gateway name\"\n- @@ -37683,4 +37940,4 @@ entries: urls: - assets/netfoundry/ziti-host-1.5.1.tgz version: 1.5.1 -generated: "2024-07-30T00:47:02.795691408Z" +generated: "2024-07-31T00:38:57.130854996Z"

    h6iFvjRtp) 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 DHgo(U diff --git a/assets/kubecost/cost-analyzer-2.3.4.tgz b/assets/kubecost/cost-analyzer-2.3.4.tgz new file mode 100644 index 0000000000000000000000000000000000000000..53e51888fbd19600fecc1e3103e18ec1208e1623 GIT binary patch literal 146322 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

  • 526hH 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 G4g~-KCY8ki literal 0 HcmV?d00001 diff --git a/assets/kubecost/cost-analyzer-2.3.3.tgz b/assets/kubecost/cost-analyzer-2.3.3.tgz index 93776eb91c3489f6998d96b0fb8404e148a81e6a..2ba707f27edf991b789d391090a71f9760285e54 100644 GIT binary patch delta 140125 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 delta 140104 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