From 8f237e2b82215250a24be199c607b0249808400a Mon Sep 17 00:00:00 2001 From: Tom Proctor Date: Wed, 19 Apr 2023 20:43:49 +0100 Subject: [PATCH] Generate helm docs --- Makefile | 8 + hack/helm-reference-gen/doc_node.go | 199 ++++++ .../fixtures/full-values.golden | 178 ++++++ .../fixtures/full-values.yaml | 183 ++++++ hack/helm-reference-gen/go.mod | 13 + hack/helm-reference-gen/go.sum | 12 + hack/helm-reference-gen/main.go | 425 +++++++++++++ hack/helm-reference-gen/main_test.go | 190 ++++++ hack/helm-reference-gen/parse_error.go | 23 + values.yaml | 601 +++++++++++------- 10 files changed, 1616 insertions(+), 216 deletions(-) create mode 100644 hack/helm-reference-gen/doc_node.go create mode 100644 hack/helm-reference-gen/fixtures/full-values.golden create mode 100644 hack/helm-reference-gen/fixtures/full-values.yaml create mode 100644 hack/helm-reference-gen/go.mod create mode 100644 hack/helm-reference-gen/go.sum create mode 100644 hack/helm-reference-gen/main.go create mode 100644 hack/helm-reference-gen/main_test.go create mode 100644 hack/helm-reference-gen/parse_error.go diff --git a/Makefile b/Makefile index 5600220..87d4464 100644 --- a/Makefile +++ b/Makefile @@ -99,3 +99,11 @@ delete-kind: kind delete cluster --name ${KIND_CLUSTER_NAME} || : .PHONY: values-schema test-image test-unit test-bats test test-acceptance test-destroy test-provision acceptance provision-cluster destroy-cluster + +# Generate Helm reference docs from values.yaml and update Vault website. +# Usage: make gen-helm-docs vault=. +# If no options are given, the local copy of docs/helm.mdx will be updated, which can +# be used to submit a PR to vault docs. +# Adapted from https://github.com/hashicorp/consul-k8s/tree/main/hack/helm-reference-gen +gen-helm-docs: + @cd hack/helm-reference-gen; go run ./... --vault=$(vault) diff --git a/hack/helm-reference-gen/doc_node.go b/hack/helm-reference-gen/doc_node.go new file mode 100644 index 0000000..a97b69f --- /dev/null +++ b/hack/helm-reference-gen/doc_node.go @@ -0,0 +1,199 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package main + +import ( + "errors" + "fmt" + "strings" +) + +const UnknownKindError = "unknown kind" + +// DocNode is a node in the final generated reference document. +// For example this would be a single DocNode: +// ``` +// - `global` ((#v-global)) - Holds values that affect multiple components of the chart. +// ``` +type DocNode struct { + // Column is the character column (i.e. indent) this node should be displayed + // at. + // For example, if this is a root node, then its column will be 0 because it + // shouldn't be indented. + Column int + + // ParentBreadcrumb is the path to this node's parent from the root. + // It is used for the HTML anchor, e.g. `#v-global-name`. + // If this node were global.name, then this would be set to "global". + ParentBreadcrumb string + + // ParentWasMap is true when the parent of this node was a map. + ParentWasMap bool + + // Key is the key of this node, e.g. if `key: value` then Key would be "key". + Key string + + // Default is the default value for this node, e.g. if key defaults to false, + // Default would be "false". + Default string + + // Comment is the YAML comment that described this node. + Comment string + + // KindTag is the YAML parsed kind tag from the YAML library. This has values + // like "!!seq" and "!!str". + KindTag string + + // Children are other nodes that should be displayed as sub-keys of this node. + Children []DocNode +} + +// Validate returns an error if this node is invalid, else nil. +func (n DocNode) Validate() error { + kind := n.FormattedKind() + if strings.Contains(kind, UnknownKindError) { + return errors.New(kind) + } + return nil +} + +// HTMLAnchor constructs the HTML anchor to be used to link to this node. +func (n DocNode) HTMLAnchor() string { + return fmt.Sprintf("%s-%s", n.ParentBreadcrumb, strings.ToLower(n.Key)) +} + +// FormattedDefault returns the default value for this node formatted properly. +func (n DocNode) FormattedDefault() string { + // Check for the annotation first. + if match := defaultAnnotation.FindAllStringSubmatch(n.Comment, -1); len(match) > 0 { + // Handle it being set > 1 time. Use the last match. + return match[len(match)-1][1] + } + + // We don't show the default if the kind is a map of arrays or map because the + // default will be too big to show inline. + if n.FormattedKind() == "array" || n.FormattedKind() == "map" { + return "" + } + + if n.Default != "" && n.Default != "null" { + // Don't show multiline string defaults since it wouldn't fit. + // We use > 2 because if it's extraConfig, e.g. `{}` then we want to + // show it but if it's affinity then it doesn't make sense to show it. + if len(strings.Split(n.Default, "\n")) > 2 { + return "" + } + if n.FormattedKind() == "string" { + return fmt.Sprintf("\"%s\"", strings.TrimSpace(n.Default)) + } + return strings.TrimSpace(n.Default) + } + + // If we get here then the default is an empty string. We return quotes + // in this case so it's clear it's an empty string. Otherwise it would look + // like: `string: ` vs. `string: ""`. + return `""` +} + +// FormattedDocumentation returns the formatted documentation for this node. +func (n DocNode) FormattedDocumentation() string { + doc := n.Comment + + // Replace all leading YAML comment characters, e.g. + // `# yaml comment` => `yaml comment`. + doc = commentPrefix.ReplaceAllString(n.Comment, "") + doc = strings.ReplaceAll(doc, "https://developer.hashicorp.com/vault/docs", "/vault/docs") + + // Indent each line of the documentation so it lines up correctly. + var indentedLines []string + var inCodeBlock bool + for i, line := range strings.Split(doc, "\n") { + if strings.HasPrefix(line, "```") { + inCodeBlock = !inCodeBlock + } + // If the line is a @type, @default or @recurse annotation we don't include it in + // the markdown description. + // This check must be before the i == 0 check because if there's only + // one line in the description and it's the type description then we + // want to discard it. + if len(typeAnnotation.FindStringSubmatch(line)) > 0 || + len(defaultAnnotation.FindStringSubmatch(line)) > 0 || + len(recurseAnnotation.FindStringSubmatch(line)) > 0 { + continue + } + + var indentedLine string + // The first line is printed inline with the key information so it + // doesn't need to be indented, e.g. + // `key - first line docs` + if i == 0 { + indentedLine = line + } else if line != "" { + if !inCodeBlock && !strings.HasPrefix(line, "```") { + indentedLines[len(indentedLines)-1] += " " + line + continue + } + indent := n.Column + 1 + if n.ParentWasMap { + indent = n.Column + } + indentedLine = strings.Repeat(" ", indent) + line + } else { + // No need to add whitespace indent to a newline. + } + indentedLines = append(indentedLines, indentedLine) + } + + // Trim all final newlines and whitespace. + return strings.TrimRight(strings.Join(indentedLines, "\n"), "\n ") +} + +// FormattedKind returns the kind of this node, e.g. string, boolean, etc. +func (n DocNode) FormattedKind() string { + // Check for the annotation first. + if match := typeAnnotation.FindAllStringSubmatch(n.Comment, -1); len(match) > 0 { + // Handle it being set > 1 time. Use the last match. + return match[len(match)-1][1] + } + + // Special case for secretName, secretKey so they don't need to set + // # type: string. + if n.Key == "secretName" || n.Key == "secretKey" { + return "string" + } + + // The YAML kind tag looks like "!!str". + switch strings.TrimLeft(n.KindTag, "!") { + case "str": + return "string" + case "int": + return "int" + case "bool": + return "boolean" + case "map": + // We don't show the kind if its of type because it's obvious it's a map + // because it will have subkeys and so showing the type as map would + // just complicate reading without any benefit. + // NOTE: If it's been explicitly annotated with @type: map then we + // will show it as that is handled above via the typeAnnotation regex + // match. + return "" + case "seq": + return "array" + case "null": + return "" + default: + return fmt.Sprintf("%s '%v'", UnknownKindError, n.KindTag) + } +} + +// LeadingIndent returns the leading indentation for the first line of this +// node. +func (n DocNode) LeadingIndent() string { + indent := n.Column - 1 + if n.ParentWasMap { + indent = n.Column - 3 + } + return strings.Repeat(" ", indent) +} diff --git a/hack/helm-reference-gen/fixtures/full-values.golden b/hack/helm-reference-gen/fixtures/full-values.golden new file mode 100644 index 0000000..703d8fc --- /dev/null +++ b/hack/helm-reference-gen/fixtures/full-values.golden @@ -0,0 +1,178 @@ +## Top-Level Stanzas + +Use these links to navigate to a particular top-level stanza. + +- [`controller`](#h-controller) +- [`metricsService`](#h-metricsservice) +- [`defaultVaultConnection`](#h-defaultvaultconnection) +- [`defaultAuthMethod`](#h-defaultauthmethod) +- [`tests`](#h-tests) + +## All Values + +### controller ((#h-controller)) + +- `controller` ((#v-controller)) - Top level configuration for the vault secrets operator deployment. + This is comprised of a controller and a kube rbac proxy container. + + - `replicas` ((#v-controller-replicas)) (`integer: 1`) - Set the number of replicas for the operator. + + - `kubeRbacProxy` ((#v-controller-kuberbacproxy)) - Settings related to the kubeRbacProxy container. This container is an HTTP proxy for the + controller manager which performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. + + - `image` ((#v-controller-kuberbacproxy-image)) - Image sets the repo and tag of the kube-rbac-proxy image to use for the controller. + + - `repository` ((#v-controller-kuberbacproxy-image-repository)) (`string: gcr.io/kubebuilder/kube-rbac-proxy`) + + - `tag` ((#v-controller-kuberbacproxy-image-tag)) (`string: v0.11.0`) + + - `resources` ((#v-controller-kuberbacproxy-resources)) (`map`) - Configures the default resources for the kube rbac proxy container. + For more information on configuring resources, see the K8s documentation: + https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + + - `limits` ((#v-controller-kuberbacproxy-resources-limits)) + + - `cpu` ((#v-controller-kuberbacproxy-resources-limits-cpu)) (`string: 500m`) + + - `memory` ((#v-controller-kuberbacproxy-resources-limits-memory)) (`string: 128Mi`) + + - `requests` ((#v-controller-kuberbacproxy-resources-requests)) + + - `cpu` ((#v-controller-kuberbacproxy-resources-requests-cpu)) (`string: 5m`) + + - `memory` ((#v-controller-kuberbacproxy-resources-requests-memory)) (`string: 64Mi`) + + - `manager` ((#v-controller-manager)) - Settings related to the vault-secrets-operator container. + + - `image` ((#v-controller-manager-image)) - Image sets the repo and tag of the vault-secrets-operator image to use for the controller. + + - `repository` ((#v-controller-manager-image-repository)) (`string: hashicorp/vault-secrets-operator`) + + - `tag` ((#v-controller-manager-image-tag)) (`string: 0.0.0-dev`) + + - `resources` ((#v-controller-manager-resources)) (`map`) - Configures the default resources for the vault-secrets-operator container. + For more information on configuring resources, see the K8s documentation: + https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + + - `limits` ((#v-controller-manager-resources-limits)) + + - `cpu` ((#v-controller-manager-resources-limits-cpu)) (`string: 500m`) + + - `memory` ((#v-controller-manager-resources-limits-memory)) (`string: 128Mi`) + + - `requests` ((#v-controller-manager-resources-requests)) + + - `cpu` ((#v-controller-manager-resources-requests-cpu)) (`string: 10m`) + + - `memory` ((#v-controller-manager-resources-requests-memory)) (`string: 64Mi`) + + - `controllerConfigMapYaml` ((#v-controller-controllerconfigmapyaml)) (`map`) - Sets the configuration settings used by the controller. Any custom changes will be reflected in the + data field of the configmap. + For more information on configuring resources, see the K8s documentation: + https://kubernetes.io/docs/concepts/configuration/configmap/ + + - `health` ((#v-controller-controllerconfigmapyaml-health)) + + - `healthProbeBindAddress` ((#v-controller-controllerconfigmapyaml-health-healthprobebindaddress)) (`string: :8081`) + + - `leaderElection` ((#v-controller-controllerconfigmapyaml-leaderelection)) + + - `leaderElect` ((#v-controller-controllerconfigmapyaml-leaderelection-leaderelect)) (`boolean: true`) + + - `resourceName` ((#v-controller-controllerconfigmapyaml-leaderelection-resourcename)) (`string: b0d477c0.hashicorp.com`) + + - `metrics` ((#v-controller-controllerconfigmapyaml-metrics)) + + - `bindAddress` ((#v-controller-controllerconfigmapyaml-metrics-bindaddress)) (`string: 127.0.0.1:8080`) + + - `webhook` ((#v-controller-controllerconfigmapyaml-webhook)) + + - `port` ((#v-controller-controllerconfigmapyaml-webhook-port)) (`integer: 9443`) + + - `kubernetesClusterDomain` ((#v-controller-kubernetesclusterdomain)) (`string: cluster.local`) - Configures the environment variable KUBERNETES_CLUSTER_DOMAIN used by KubeDNS. + +### metricsService ((#h-metricsservice)) + +- `metricsService` ((#v-metricsservice)) (`map`) - Configure the metrics service ports used by the metrics service. + Set the configuration fo the metricsService port. + + - `ports` ((#v-metricsservice-ports)) (`map`) - Set the port settings for the metrics service. + For more information on configuring resources, see the K8s documentation: + https://kubernetes.io/docs/concepts/services-networking/service/ + + - `name` ((#v-metricsservice-ports-name)) (`string: https`) + + - `port` ((#v-metricsservice-ports-port)) (`integer: 8443`) + + - `protocol` ((#v-metricsservice-ports-protocol)) (`string: TCP`) + + - `targetPort` ((#v-metricsservice-ports-targetport)) (`string: https`) + + - `type` ((#v-metricsservice-type)) (`string: ClusterIP`) + +### defaultVaultConnection ((#h-defaultvaultconnection)) + +- `defaultVaultConnection` ((#v-defaultvaultconnection)) - Configures the default VaultConnection CR which will be used by resources + if they do not specify a VaultConnection reference. The name is 'default' and will + always be installed in the same namespace as the operator. + + - `enabled` ((#v-defaultvaultconnection-enabled)) (`boolean: false`) - toggles the deployment of the VaultAuthMethod CR + + - `address` ((#v-defaultvaultconnection-address)) (`string: http://vault.default.svc.cluster.local:8200`) - Address of the Vault Server + + - `caCertSecret` ((#v-defaultvaultconnection-cacertsecret)) (`string: ""`) - CACertSecret containing the trusted PEM encoded CA certificate chain. + Note: This secret must exist prior to deploying the CR. + + - `tlsServerName` ((#v-defaultvaultconnection-tlsservername)) (`string: ""`) - TLSServerName to use as the SNI host for TLS connections. + + - `skipTLSVerify` ((#v-defaultvaultconnection-skiptlsverify)) (`boolean: false`) - SkipTLSVerify for TLS connections. + + - `headers` ((#v-defaultvaultconnection-headers)) (`string: ""`) - Headers to be included in all Vault requests. + headers: | + "vault-something1": "foo" + "vault-something2": "bar" + "vault-something3": "baz" + +### defaultAuthMethod ((#h-defaultauthmethod)) + +- `defaultAuthMethod` ((#v-defaultauthmethod)) - Configures and deploys the default VaultAuthMethod CR which will be used by resources + if they do not specify a VaultAuthMethod reference. The name is 'default' and will + always be installed in the same namespace as the operator. + + - `enabled` ((#v-defaultauthmethod-enabled)) (`boolean: false`) - toggles the deployment of the VaultAuthMethod CR + + - `namespace` ((#v-defaultauthmethod-namespace)) (`string: default`) - Vault namespace for the VaultAuthMethod CR + + - `method` ((#v-defaultauthmethod-method)) (`string: kubernetes`) - Vault Auth method to be used with the VaultAuthMethod CR + + - `mount` ((#v-defaultauthmethod-mount)) (`string: kubernetes`) - Mount path for the Vault Auth Method. + + - `kubernetes` ((#v-defaultauthmethod-kubernetes)) - Vault Kubernetes auth method specific configuration + + - `role` ((#v-defaultauthmethod-kubernetes-role)) (`string: demo`) - Vault Auth Role to use + This is a required field and must be setup in Vault prior to deploying the helm chart + if `defaultAuthMethod.enabled=true` + TODO: mark required in the charts. + + - `serviceAccount` ((#v-defaultauthmethod-kubernetes-serviceaccount)) (`string: default`) - Kubernetes ServiceAccount associated with the default Vault Auth Role + + - `tokenAudiences` ((#v-defaultauthmethod-kubernetes-tokenaudiences)) (`array: []`) - Token Audience is required and should match whatever the audience + of the vault kubernetes auth role has set. + + - `params` ((#v-defaultauthmethod-params)) (`string: ""`) - Params to use when authenticating to Vault + params: | + "vault-something1": "foo" + "vault-something2": "bar" + "vault-something3": "baz" + + - `headers` ((#v-defaultauthmethod-headers)) (`string: ""`) - Headers to be included in all Vault requests. + headers: | + "vault-something1": "foo" + "vault-something2": "bar" + "vault-something3": "baz" + +### tests ((#h-tests)) + +- `tests` ((#v-tests)) - # Used by unit tests, and will not be rendered except when using `helm template`, this can be safely ignored. + + - `enabled` ((#v-tests-enabled)) (`boolean: true`) diff --git a/hack/helm-reference-gen/fixtures/full-values.yaml b/hack/helm-reference-gen/fixtures/full-values.yaml new file mode 100644 index 0000000..564f291 --- /dev/null +++ b/hack/helm-reference-gen/fixtures/full-values.yaml @@ -0,0 +1,183 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# Top level configuration for the vault secrets operator deployment. +# This is comprised of a controller and a kube rbac proxy container. +controller: + + # Set the number of replicas for the operator. + # @type: integer + replicas: 1 + + # Settings related to the kubeRbacProxy container. This container is an HTTP proxy for the + # controller manager which performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. + kubeRbacProxy: + # Image sets the repo and tag of the kube-rbac-proxy image to use for the controller. + image: + repository: gcr.io/kubebuilder/kube-rbac-proxy + tag: v0.11.0 + + # Configures the default resources for the kube rbac proxy container. + # For more information on configuring resources, see the K8s documentation: + # https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + # @recurse: true + # @type: map + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 5m + memory: 64Mi + + # Settings related to the vault-secrets-operator container. + manager: + # Image sets the repo and tag of the vault-secrets-operator image to use for the controller. + image: + repository: hashicorp/vault-secrets-operator + tag: 0.0.0-dev + + # Configures the default resources for the vault-secrets-operator container. + # For more information on configuring resources, see the K8s documentation: + # https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + # @recurse: true + # @type: map + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + + # Sets the configuration settings used by the controller. Any custom changes will be reflected in the + # data field of the configmap. + # For more information on configuring resources, see the K8s documentation: + # https://kubernetes.io/docs/concepts/configuration/configmap/ + # @recurse: true + # @type: map + controllerConfigMapYaml: + health: + healthProbeBindAddress: :8081 + leaderElection: + leaderElect: true + resourceName: b0d477c0.hashicorp.com + metrics: + bindAddress: 127.0.0.1:8080 + webhook: + port: 9443 + + # Configures the environment variable KUBERNETES_CLUSTER_DOMAIN used by KubeDNS. + # @type: string + kubernetesClusterDomain: cluster.local + + +# Configure the metrics service ports used by the metrics service. +# Set the configuration fo the metricsService port. +# @recurse: true +# @type: map +metricsService: + # Set the port settings for the metrics service. + # For more information on configuring resources, see the K8s documentation: + # https://kubernetes.io/docs/concepts/services-networking/service/ + # @type: map + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: https + type: ClusterIP + +# Configures the default VaultConnection CR which will be used by resources +# if they do not specify a VaultConnection reference. The name is 'default' and will +# always be installed in the same namespace as the operator. +defaultVaultConnection: + # toggles the deployment of the VaultAuthMethod CR + # @type: boolean + enabled: false + + # Address of the Vault Server + # @type: string + address: http://vault.default.svc.cluster.local:8200 + + # CACertSecret containing the trusted PEM encoded CA certificate chain. + # Note: This secret must exist prior to deploying the CR. + # @type: string + caCertSecret: "" + + # TLSServerName to use as the SNI host for TLS connections. + # @type: string + tlsServerName: "" + + # SkipTLSVerify for TLS connections. + # @type: boolean + skipTLSVerify: false + + # Headers to be included in all Vault requests. + # @type: string, eg: + # headers: | + # "vault-something1": "foo" + # "vault-something2": "bar" + # "vault-something3": "baz" + # @type: string + headers: "" + + +# Configures and deploys the default VaultAuthMethod CR which will be used by resources +# if they do not specify a VaultAuthMethod reference. The name is 'default' and will +# always be installed in the same namespace as the operator. +defaultAuthMethod: + # toggles the deployment of the VaultAuthMethod CR + # @type: boolean + enabled: false + + # Vault namespace for the VaultAuthMethod CR + # @type: string + namespace: default + + # Vault Auth method to be used with the VaultAuthMethod CR + # @type: string + method: kubernetes + + # Mount path for the Vault Auth Method. + # @type: string + mount: kubernetes + + # Vault Kubernetes auth method specific configuration + kubernetes: + # Vault Auth Role to use + # This is a required field and must be setup in Vault prior to deploying the helm chart + # if `defaultAuthMethod.enabled=true` + # TODO: mark required in the charts. + # @type: string + role: demo + + # Kubernetes ServiceAccount associated with the default Vault Auth Role + # @type: string + serviceAccount: default + + # Token Audience is required and should match whatever the audience + # of the vault kubernetes auth role has set. + # @type: array + tokenAudiences: [] + + # Params to use when authenticating to Vault + # params: | + # "vault-something1": "foo" + # "vault-something2": "bar" + # "vault-something3": "baz" + # @type: string + params: "" + + # Headers to be included in all Vault requests. + # headers: | + # "vault-something1": "foo" + # "vault-something2": "bar" + # "vault-something3": "baz" + # @type: string + headers: "" + +## Used by unit tests, and will not be rendered except when using `helm template`, this can be safely ignored. +tests: + # @type: boolean + enabled: true diff --git a/hack/helm-reference-gen/go.mod b/hack/helm-reference-gen/go.mod new file mode 100644 index 0000000..2474b6d --- /dev/null +++ b/hack/helm-reference-gen/go.mod @@ -0,0 +1,13 @@ +module github.com/hashicorp/consul-k8s/hack/helm-reference-gen + +go 1.19 + +require ( + github.com/stretchr/testify v1.6.1 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/davecgh/go-spew v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect +) diff --git a/hack/helm-reference-gen/go.sum b/hack/helm-reference-gen/go.sum new file mode 100644 index 0000000..b624904 --- /dev/null +++ b/hack/helm-reference-gen/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hack/helm-reference-gen/main.go b/hack/helm-reference-gen/main.go new file mode 100644 index 0000000..30dd7c0 --- /dev/null +++ b/hack/helm-reference-gen/main.go @@ -0,0 +1,425 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package main + +// This script generates markdown documentation out of the values.yaml file +// for use on vault.io. +// +// Usage: make gen-helm-docs [vault-repo-path] [-validate] +// Where [vault-repo-path] is the location of the hashicorp/vault repo. Defaults to ../../../vault. +// If -validate is set, the generated docs won't be output anywhere. +// This is useful in CI to ensure the generation will succeed. + +import ( + "bytes" + "flag" + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + "text/template" + + "gopkg.in/yaml.v3" +) + +const ( + tocPrefix = "## Top-Level Stanzas\n\nUse these links to navigate to a particular top-level stanza.\n\n" + tocSuffix = "\n## All Values" +) + +var ( + // typeAnnotation matches the @type annotation. It captures the value of @type. + typeAnnotation = regexp.MustCompile(`(?m).*@type: (.*)$`) + + // defaultAnnotation matches the @default annotation. It captures the value of @default. + defaultAnnotation = regexp.MustCompile(`(?m).*@default: (.*)$`) + + // recurseAnnotation matches the @recurse annotation. It captures the value of @recurse. + recurseAnnotation = regexp.MustCompile(`(?m).*@recurse: (.*)$`) + + // commentPrefix matches on the YAML comment prefix, e.g. + // ``` + // # comment here + // # comment with indent + // ``` + // Will match on "comment here" and "comment with indent". + // + // It also properly handles YAML comments inside code fences, e.g. + // ``` + // # Example: + // # ```yaml + // # # yaml comment + // # ```` + // ``` + // And will not match the "# yaml comment" incorrectly. + commentPrefix = regexp.MustCompile(`(?m)^[^\S\n]*#[^\S\n]?`) + + funcMap = template.FuncMap{ + "ToLower": strings.ToLower, + } + + // docNodeTmpl is the go template used to print a DocNode node. + // We use $ instead of ` in the template so we can use the golang raw string + // format. We then do the replace from $ => `. + docNodeTmpl = template.Must( + template.New("").Funcs(funcMap).Parse( + strings.Replace( + `{{- if eq .Column 1 }}## {{ .Key }} + +{{ end }}{{ .LeadingIndent }}- ${{ .Key }}${{ if ne .FormattedKind "" }} (${{ .FormattedKind }}{{ if .FormattedDefault }}: {{ .FormattedDefault }}{{ end }}$){{ end }}{{ if .FormattedDocumentation}} - {{ .FormattedDocumentation }}{{ end }}`, + "$", "`", -1)), + ) +) + +func main() { + validateFlag := flag.Bool("validate", false, "only validate that the markdown can be generated, don't actually generate anything") + vaultMdxFlag := flag.String("vault", "", "path to the helm reference documentation file") + chartDocsPath := "../../" + flag.Parse() + + if *vaultMdxFlag == "" { + *vaultMdxFlag = "docs/helm.mdx" + } + + if len(os.Args) > 3 { + fmt.Println("Error: extra arguments") + os.Exit(1) + } + + if !*validateFlag { + // Only argument is path to Vault repo. If not set then we default. + if len(os.Args) < 2 { + abs, _ := filepath.Abs(chartDocsPath) + fmt.Printf("Defaulting to Vault repo path: %s\n", abs) + } else { + // Support absolute and relative paths to the Vault repo. + if filepath.IsAbs(os.Args[1]) { + chartDocsPath = os.Args[1] + } else { + chartDocsPath = filepath.Join("../..", *vaultMdxFlag) + } + abs, _ := filepath.Abs(chartDocsPath) + fmt.Printf("Using Vault repo path: %s\n", abs) + } + } + + // Parse the values.yaml file. + inputBytes, err := os.ReadFile("../../values.yaml") + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + out, err := GenerateDocs(string(inputBytes)) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + + // If we're just validating that generation will succeed then we're done. + if *validateFlag { + fmt.Println("Validation successful") + os.Exit(0) + } + + // Otherwise we'll go on to write the changes to the helm docs. + helmReferenceFile := filepath.Join(chartDocsPath) + helmReferenceBytes, err := os.ReadFile(helmReferenceFile) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + helmReferenceContents := string(helmReferenceBytes) + + // Swap out the contents between the codegen markers. + startStr := "\n\n" + endStr := "\n" + start := strings.Index(helmReferenceContents, startStr) + if start == -1 { + fmt.Printf("%q not found in %q\n", startStr, helmReferenceFile) + os.Exit(1) + } + end := strings.Index(helmReferenceContents, endStr) + if end == -1 { + fmt.Printf("%q not found in %q\n", endStr, helmReferenceFile) + os.Exit(1) + } + + newMdx := helmReferenceContents[0:start+len(startStr)] + out + helmReferenceContents[end:] + err = os.WriteFile(helmReferenceFile, []byte(newMdx), 0o644) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + abs, _ := filepath.Abs(helmReferenceFile) + fmt.Printf("Updated with generated docs: %s\n", abs) +} + +func GenerateDocs(yamlStr string) (string, error) { + node, err := Parse(yamlStr) + if err != nil { + return "", fmt.Errorf("failed to parse values.yaml: %w", err) + } + + children, err := generateDocsFromNode(docNodeTmpl, node) + if err != nil { + return "", err + } + + // enterpriseSubst := strings.ReplaceAll(strings.Join(children, "\n\n"), "[Enterprise Only]", "") + + // // Add table of contents. + // toc := generateTOC(node) + return strings.Join(children, "\n\n") + "\n\n", nil +} + +// Parse parses yamlStr into a tree of DocNode's. +func Parse(yamlStr string) (DocNode, error) { + var node yaml.Node + err := yaml.Unmarshal([]byte(yamlStr), &node) + if err != nil { + return DocNode{}, err + } + + // Due to how the YAML is parsed this is the first real node. + rootNode := node.Content[0].Content + children, err := parseNodeContent(rootNode, "", false) + if err != nil { + return DocNode{}, err + } + return DocNode{ + Column: 0, + Children: children, + }, nil +} + +// parseNodeContent recursively parses the yaml nodes and outputs a DocNode +// tree. +func parseNodeContent(nodeContent []*yaml.Node, parentBreadcrumb string, parentWasMap bool) ([]DocNode, error) { + var docNodes []DocNode + + // This is a special type of node where it's an array of maps. + // e.g. + // ```` + // ingressGateways: + // - name: name + // ```` + // + // In this case we show the docs as: + // - ingress-gateway: ingress gateway descrip + // - name: name descrip. + // + // To do that, we actually need to skip the map node. + if len(nodeContent) == 1 { + return parseNodeContent(nodeContent[0].Content, parentBreadcrumb, true) + } + + // skipNext is true if we should skip the next node. Due to how the YAML is + // parsed, a key: value pair results in two YAML nodes but we only need + // doc node out of that so in the loop we look ahead to the next node + // and use it to construct our DocNode. Then we can skip it on the next + // iteration. + skipNext := false + for i, child := range nodeContent { + if skipNext { + skipNext = false + continue + } + + docNode, err := buildDocNode(i, child, nodeContent, parentBreadcrumb, parentWasMap) + if err != nil { + return nil, err + } + + if err := docNode.Validate(); err != nil { + return nil, &ParseError{ + FullAnchor: docNode.HTMLAnchor(), + Err: err.Error(), + } + } + + docNodes = append(docNodes, docNode) + skipNext = true + continue + } + return docNodes, nil +} + +func generateDocsFromNode(tm *template.Template, node DocNode) ([]string, error) { + var out []string + for _, child := range node.Children { + var nodeOut bytes.Buffer + err := tm.Execute(&nodeOut, child) + if err != nil { + return nil, err + } + childOut, err := generateDocsFromNode(tm, child) + if err != nil { + return nil, err + } + out = append(append(out, nodeOut.String()), childOut...) + } + return out, nil +} + +// allScalars returns true if content contains only scalar nodes +// with no chidren. +func allScalars(content []*yaml.Node) bool { + for _, n := range content { + if n.Kind != yaml.ScalarNode || len(n.Content) > 0 { + return false + } + } + return true +} + +// toInlineYaml will return the yaml string representation for content +// using the inline representation, i.e. `["a", "b"]` +// instead of: +// ``` +// - "a" +// - "b" +// ``` +func toInlineYaml(content []*yaml.Node) (string, error) { + // We have to use this struct so we can set the struct tag "flow" so the + // generated yaml uses the inline format. + type intermediary struct { + Arr []*yaml.Node `yaml:"arr,flow"` + } + i := intermediary{ + Arr: content, + } + out, err := yaml.Marshal(i) + if err != nil { + return "", err + } + // Hack: because we had to use our struct, it has the key "arr: " which + // we need to trim. Before trimming it will look like: + // `arr: ["a","b"]`. + return strings.TrimPrefix(string(out), "arr: "), nil +} + +func buildDocNode(nodeContentIdx int, currNode *yaml.Node, nodeContent []*yaml.Node, parentBreadcrumb string, parentWasMap bool) (DocNode, error) { + // Check for the @recurse: false annotation. + // In this case we construct our node and then don't recurse further. + if match := recurseAnnotation.FindStringSubmatch(currNode.HeadComment); len(match) > 0 && match[1] == "false" { + return DocNode{ + Column: currNode.Column, + ParentBreadcrumb: parentBreadcrumb, + ParentWasMap: false, + Key: currNode.Value, + Comment: currNode.HeadComment, + }, nil + } + + // Nodes should come in pairs. + if len(nodeContent) < nodeContentIdx+1 { + return DocNode{}, &ParseError{ + ParentAnchor: parentBreadcrumb, + CurrAnchor: currNode.Value, + Err: fmt.Sprintf("content length incorrect, expected %d got %d", nodeContentIdx+1, len(nodeContent)), + } + } + + next := nodeContent[nodeContentIdx+1] + + switch next.Kind { + + // If it's a scalar then this is a simple key: value node. + case yaml.ScalarNode: + return DocNode{ + ParentBreadcrumb: parentBreadcrumb, + ParentWasMap: parentWasMap, + Column: currNode.Column, + Key: currNode.Value, + Comment: currNode.HeadComment, + KindTag: next.Tag, + Default: next.Value, + }, nil + + // If it's a map then we will need to recurse into it. + case yaml.MappingNode: + docNode := DocNode{ + ParentBreadcrumb: parentBreadcrumb, + ParentWasMap: parentWasMap, + Column: currNode.Column, + Key: currNode.Value, + Comment: currNode.HeadComment, + KindTag: next.Tag, + } + var err error + docNode.Children, err = parseNodeContent(next.Content, docNode.HTMLAnchor(), false) + if err != nil { + return DocNode{}, err + } + return docNode, nil + + // If it's a sequence, i.e. array, then we have to handle it differently + // depending on its contents. + case yaml.SequenceNode: + // If it's empty then its just a key with a default of empty array. + if len(next.Content) == 0 { + return DocNode{ + ParentBreadcrumb: parentBreadcrumb, + ParentWasMap: parentWasMap, + Column: currNode.Column, + Key: currNode.Value, + // Default is empty array. + Default: "[]", + Comment: currNode.HeadComment, + KindTag: next.Tag, + }, nil + + // If it's full of scalars, e.g. key: [a, b] then we can stop recursing + // and use the value as the default. + } else if allScalars(next.Content) { + inlineYaml, err := toInlineYaml(next.Content) + if err != nil { + return DocNode{}, &ParseError{ + ParentAnchor: parentBreadcrumb, + CurrAnchor: currNode.Value, + Err: err.Error(), + } + } + return DocNode{ + ParentBreadcrumb: parentBreadcrumb, + ParentWasMap: parentWasMap, + Column: currNode.Column, + Key: currNode.Value, + // Default will be the yaml value. + Default: inlineYaml, + Comment: currNode.HeadComment, + KindTag: next.Tag, + }, nil + } else { + + // Otherwise we need to recurse into each element of the array. + docNode := DocNode{ + ParentBreadcrumb: parentBreadcrumb, + ParentWasMap: parentWasMap, + Column: currNode.Column, + Key: currNode.Value, + Comment: currNode.HeadComment, + KindTag: next.Tag, + } + var err error + docNode.Children, err = parseNodeContent(next.Content, docNode.HTMLAnchor(), false) + if err != nil { + return DocNode{}, err + } + return docNode, nil + } + } + return DocNode{}, fmt.Errorf("fell through cases unexpectedly at breadcrumb: %s", parentBreadcrumb) +} + +func generateTOC(node DocNode) string { + toc := tocPrefix + + for _, c := range node.Children { + toc += fmt.Sprintf("- [`%s`](#h-%s)\n", c.Key, strings.ToLower(c.Key)) + } + + return toc + tocSuffix +} diff --git a/hack/helm-reference-gen/main_test.go b/hack/helm-reference-gen/main_test.go new file mode 100644 index 0000000..d20ba6a --- /dev/null +++ b/hack/helm-reference-gen/main_test.go @@ -0,0 +1,190 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package main + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +// Test various smaller cases and special cases. +func Test(t *testing.T) { + cases := map[string]struct { + Input string + Exp string + }{ + "string value": { + Input: `--- +# Line 1 +# Line 2 +key: value`, + Exp: `- [$key$](#h-key) + +## All Values + +### key ((#h-key)) + +- $key$ ((#v-key)) ($string: value$) - Line 1\n Line 2 +`, + }, + "integer value": { + Input: `--- +# Line 1 +# Line 2 +replicas: 3`, + Exp: `- [$replicas$](#h-replicas) + +## All Values + +### replicas ((#h-replicas)) + +- $replicas$ ((#v-replicas)) ($integer: 3$) - Line 1\n Line 2 +`, + }, + "boolean value": { + Input: `--- +# Line 1 +# Line 2 +enabled: true`, + Exp: `- [$enabled$](#h-enabled) + +## All Values + +### enabled ((#h-enabled)) + +- $enabled$ ((#v-enabled)) ($boolean: true$) - Line 1\n Line 2 +`, + }, + "map": { + Input: `--- +# Map line 1 +# Map line 2 +map: + # Key line 1 + # Key line 2 + key: value`, + Exp: `- [$map$](#h-map) + +## All Values + +### map ((#h-map)) + +- $map$ ((#v-map)) - Map line 1\n Map line 2 + + - $key$ ((#v-map-key)) ($string: value$) - Key line 1\n Key line 2 +`, + }, + "map with multiple keys": { + Input: `--- +# Map line 1 +# Map line 2 +map: + # Key line 1 + # Key line 2 + key: value + # Int docs + int: 1 + # Bool docs + bool: true`, + Exp: `- [$map$](#h-map) + +## All Values + +### map ((#h-map)) + +- $map$ ((#v-map)) - Map line 1\n Map line 2 + + - $key$ ((#v-map-key)) ($string: value$) - Key line 1 + Key line 2 + + - $int$ ((#v-map-int)) ($integer: 1$) - Int docs + + - $bool$ ((#v-map-bool)) ($boolean: true$) - Bool docs +`, + }, + "null value": { + Input: `--- +# key docs +# @type: string +key: null`, + Exp: `- [$key$](#h-key) + +## All Values + +### key ((#h-key)) + +- $key$ ((#v-key)) ($string: null$) - key docs +`, + }, + "description with empty line": { + Input: `--- +# line 1 +# +# line 2 +key: value`, + Exp: `- [$key$](#h-key) + +## All Values + +### key ((#h-key)) + +- $key$ ((#v-key)) ($string: value$) - line 1\n\n line 2 +`, + }, + "array of strings": { + Input: `--- +# line 1 +# @type: array +serverAdditionalDNSSANs: [] +`, + Exp: `- [$serverAdditionalDNSSANs$](#h-serveradditionaldnssans) + +## All Values + +### serverAdditionalDNSSANs ((#h-serveradditionaldnssans)) + +- $serverAdditionalDNSSANs$ ((#v-serveradditionaldnssans)) ($array: []$) - line 1 +`, + }, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + // Swap $ for `. + input := strings.Replace(c.Input, "$", "`", -1) + + out, err := GenerateDocs(input) + require.NoError(t, err) + + // Swap $ for `. + exp := strings.Replace(c.Exp, "$", "`", -1) + + // Swap \n for real \n. + exp = strings.Replace(exp, "\\n", "\n", -1) + + exp = tocPrefix + exp + + require.Equal(t, exp, out) + }) + } +} + +// Test against a full values file and compare against a golden file. +func TestFullValues(t *testing.T) { + inputBytes, err := os.ReadFile(filepath.Join("fixtures", "full-values.yaml")) + require.NoError(t, err) + expBytes, err := os.ReadFile(filepath.Join("fixtures", "full-values.golden")) + require.NoError(t, err) + + actual, err := GenerateDocs(string(inputBytes)) + require.NoError(t, err) + if actual != string(expBytes) { + require.NoError(t, os.WriteFile(filepath.Join("fixtures", "full-values.actual"), []byte(actual), 0o644)) + require.FailNow(t, "output not equal, actual output to full-values.actual") + } +} diff --git a/hack/helm-reference-gen/parse_error.go b/hack/helm-reference-gen/parse_error.go new file mode 100644 index 0000000..914737d --- /dev/null +++ b/hack/helm-reference-gen/parse_error.go @@ -0,0 +1,23 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package main + +import "fmt" + +// ParseError is an error that occurs during parsing. +// It's used to include information about which node failed to parse. +type ParseError struct { + ParentAnchor string + CurrAnchor string + FullAnchor string + Err string +} + +func (p *ParseError) Error() string { + anchor := p.FullAnchor + if anchor == "" { + anchor = fmt.Sprintf("%s-%s", p.ParentAnchor, p.CurrAnchor) + } + return fmt.Sprintf("%s: %s", anchor, p.Err) +} diff --git a/values.yaml b/values.yaml index 9e35ac8..2e16f7c 100644 --- a/values.yaml +++ b/values.yaml @@ -3,187 +3,260 @@ # Available parameters and their default values for the Vault chart. +# These global values affect multiple components of the chart. global: - # enabled is the master enabled switch. Setting this to true or false - # will enable or disable all the components within this chart by default. + # The global enabled/disabled configuration. If this is true, most components + # will be installed by default. If this is false, no components will be + # installed by default and manually opting-in is required, such as by setting + # `server.enabled` to true. enabled: true - # Image pull secret to use for registry authentication. - # Alternatively, the value may be specified as an array of strings. - imagePullSecrets: [] + # References secrets to be used when pulling images from private registries. + # See [Pull an Image from a Private Registry](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/) + # for more details. May be specified as an array of name map entries or just + # as an array of names: + # + # ```yaml # imagePullSecrets: # - name: image-pull-secret + # # or + # imagePullSecrets: + # - image-pull-secret + # ``` + imagePullSecrets: [] - # TLS for end-to-end encrypted transport + # When set to `true`, changes URLs from `https` to `http` (such as the + # `VAULT_ADDR=http://127.0.0.1:8200` environment variable set on the + # Vault pods). tlsDisable: true # External vault server address for the injector and CSI provider to use. - # Setting this will disable deployment of a vault server. + # Setting this will disable deployment of a vault server. A service account + # with token review permissions is automatically created if + # `server.serviceAccount.create=true` is set for the external Vault server + # to use. externalVaultAddr: "" - # If deploying to OpenShift + # If `true`, enables configuration specific to OpenShift such as NetworkPolicy, + # SecurityContext, and Route. openshift: false - # Create PodSecurityPolicy for pods + # Values that configure Pod Security Policy. psp: + # When set to `true`, enables Pod Security Policies for Vault and Vault Agent Injector. enable: false - # Annotation for PodSecurityPolicy. - # This is a multi-line templated string map, and can also be set as YAML. + # This value defines additional annotations to add to the Pod Security + # Policies. This can either be YAML or a YAML-formatted multi-line templated + # string. + # + # ```yaml + # annotations: + # seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default,runtime/default + # apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default + # seccomp.security.alpha.kubernetes.io/defaultProfileName: runtime/default + # apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default + # # or + # annotations: | + # seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default,runtime/default + # apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default + # seccomp.security.alpha.kubernetes.io/defaultProfileName: runtime/default + # apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default + # ``` annotations: | seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default,runtime/default apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default seccomp.security.alpha.kubernetes.io/defaultProfileName: runtime/default apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default + # Values that configure metrics and telemetry. serverTelemetry: - # Enable integration with the Prometheus Operator - # See the top level serverTelemetry section below before enabling this feature. + # When set to `true`, enables integration with the Prometheus Operator. Be + # sure to configure the top-level [`serverTelemetry`](https://developer.hashicorp.com/vault/docs/platform/k8s/helm/configuration#servertelemetry-1) + # section for more details and required configuration values. prometheusOperator: false +# Values that configure running a Vault Agent Injector Admission Webhook +# Controller within Kubernetes. injector: - # True if you want to enable vault agent injection. - # @default: global.enabled + # When set to `true`, the Vault Agent Injector Admission Webhook controller + # will be created. When set to `"-"`, defaults to the value of `global.enabled`. + # @type: boolean or string enabled: "-" - - replicas: 1 - - # Configures the port the injector should listen on - port: 8080 - - # If multiple replicas are specified, by default a leader will be determined - # so that only one injector attempts to create TLS certificates. - leaderElector: - enabled: true - - # If true, will enable a node exporter metrics endpoint at /metrics. - metrics: - enabled: false - - # Deprecated: Please use global.externalVaultAddr instead. + + # Deprecated: Please use [global.externalVaultAddr](https://developer.hashicorp.com/vault/docs/platform/k8s/helm/configuration#externalvaultaddr) + # instead. externalVaultAddr: "" - # image sets the repo and tag of the vault-k8s image to use for the injector. + # The number of pods to deploy to create a highly available cluster of Vault + # Agent Injectors. Requires Vault K8s 0.7.0 to have more than 1 replica. + replicas: 1 + + # Values that configure the Vault Agent Injector leader election for HA + # deployments. + leaderElector: + # When set to `true`, enables leader election for Vault Agent Injector. This + # is required when using auto-tls and more than 1 replica. + enabled: true + + # Values that configure the Vault Agent Injector Docker image. image: + # The name of the Docker image for Vault Agent Injector. repository: "hashicorp/vault-k8s" + # The tag of the Docker image for the Vault Agent Injector. **This should + # be pinned to a specific version when running in production.** Otherwise, + # other changes to the chart may inadvertently upgrade your admission controller. tag: "1.2.1" + # The pull policy for container images. The default pull policy is `IfNotPresent` + # which causes the Kubelet to skip pulling an image if it already exists. pullPolicy: IfNotPresent - # agentImage sets the repo and tag of the Vault image to use for the Vault Agent - # containers. This should be set to the official Vault image. Vault 1.3.1+ is - # required. + # Values that configure the Vault Agent sidecar image. agentImage: + # The name of the Docker image for the Vault Agent sidecar. This should be + # set to the official Vault Docker image. repository: "hashicorp/vault" + # The tag of the Vault Docker image to use for the Vault Agent Sidecar. + # **Vault 1.3.1+ is required by the admission controller**. tag: "1.13.1" - # The default values for the injected Vault Agent containers. + # Values that configure the injected Vault Agent containers default values. + # For more information on configuring resources, see the Kubernetes documentation: + # https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ agentDefaults: - # For more information on configuring resources, see the K8s documentation: - # https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + # The default CPU limit for injected Vault Agent containers. cpuLimit: "500m" + # The default CPU request for injected Vault Agent containers. cpuRequest: "250m" + # The default memory limit for injected Vault Agent containers. memLimit: "128Mi" + # The default memory request for injected Vault Agent containers. memRequest: "64Mi" - # Default template type for secrets when no custom template is specified. - # Possible values include: "json" and "map". + # The default template type for rendered secrets if no custom templates are + # defined. Possible values include `map` and `json`. template: "map" - # Default values within Agent's template_config stanza. + # Default values within Agent's [`template_config` stanza](https://developer.hashicorp.com/vault/docs/agent/template). templateConfig: + # Controls whether Vault Agent exits after it has exhausted its number of + # template retry attempts due to failures. exitOnRetryFailure: true + # Configures how often Vault Agent Template should render non-leased secrets + # such as KV v2. See the [Vault Agent Templates documentation](/docs/agent/template#non-renewable-secrets) + # for more details. staticSecretRenderInterval: "" - # Used to define custom livenessProbe settings - livenessProbe: - # When a probe fails, Kubernetes will try failureThreshold times before giving up - failureThreshold: 2 - # Number of seconds after the container has started before probe initiates - initialDelaySeconds: 5 - # How often (in seconds) to perform the probe - periodSeconds: 2 - # Minimum consecutive successes for the probe to be considered successful after having failed - successThreshold: 1 - # Number of seconds after which the probe times out. - timeoutSeconds: 5 - # Used to define custom readinessProbe settings - readinessProbe: - # When a probe fails, Kubernetes will try failureThreshold times before giving up - failureThreshold: 2 - # Number of seconds after the container has started before probe initiates - initialDelaySeconds: 5 - # How often (in seconds) to perform the probe - periodSeconds: 2 - # Minimum consecutive successes for the probe to be considered successful after having failed - successThreshold: 1 - # Number of seconds after which the probe times out. - timeoutSeconds: 5 - # Used to define custom startupProbe settings - startupProbe: - # When a probe fails, Kubernetes will try failureThreshold times before giving up - failureThreshold: 12 - # Number of seconds after the container has started before probe initiates - initialDelaySeconds: 5 - # How often (in seconds) to perform the probe - periodSeconds: 5 - # Minimum consecutive successes for the probe to be considered successful after having failed - successThreshold: 1 - # Number of seconds after which the probe times out. - timeoutSeconds: 5 + # Values that configure the Vault Agent Injector metric exporter. + metrics: + # When set to `true`, the Vault Agent Injector exports Prometheus metrics at + # the `/metrics` path. + enabled: false - # Mount Path of the Vault Kubernetes Auth Method. + # Mount path of the Vault Kubernetes Auth Method. authPath: "auth/kubernetes" - # Configures the log verbosity of the injector. - # Supported log levels include: trace, debug, info, warn, error + # Configures the log verbosity of the injector. Supported log levels: trace, + # debug, error, warn, info. logLevel: "info" - # Configures the log format of the injector. Supported log formats: "standard", "json". + # Configures the log format of the injector. Supported log formats: "standard", + # "json". logFormat: "standard" - # Configures all Vault Agent sidecars to revoke their token when shutting down + # Configures all Vault Agent sidecars to revoke their token when shutting down. revokeOnShutdown: false - webhook: - # Configures failurePolicy of the webhook. The "unspecified" default behaviour depends on the - # API Version of the WebHook. - # To block pod creation while the webhook is unavailable, set the policy to `Fail` below. - # See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#failure-policy + # Security context for the pod template and the injector container. + securityContext: + # Defines the securityContext for the injector Pod, as YAML or a YAML-formatted + # multi-line templated string. Default if not specified: # + # ```yaml + # runAsNonRoot: true + # runAsGroup: {{ .Values.injector.gid | default 1000 }} + # runAsUser: {{ .Values.injector.uid | default 100 }} + # fsGroup: {{ .Values.injector.gid | default 1000 }} + # ``` + # @type: dictionary + # @default: {} + pod: {} + # Defines the securityContext for the injector container, as YAML or a + # YAML-formatted multi-line templated string. Default if not specified: + # + # ```yaml + # allowPrivilegeEscalation: false + # capabilities: + # drop: + # - ALL + # ``` + # @type: dictionary + # @default: {} + container: {} + + # The resource requests and limits (CPU, memory, etc.) for each container of + # the injector. This should be a YAML dictionary of a Kubernetes + # [ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#resourcerequirements-v1-core) + # object. If this isn't specified, then the pods won't request any specific + # amount of resources, which limits the ability for Kubernetes to make + # efficient use of compute resources. + # + # **Setting this is highly recommended.** + # + # ```yaml + # resources: + # requests: + # memory: '256Mi' + # cpu: '250m' + # limits: + # memory: '256Mi' + # cpu: '250m' + # ``` + # @type: dictionary + # @default: {} + resources: {} + + # Values that control the Mutating Webhook Configuration. + webhook: + # Configures failurePolicy of the webhook. To block pod creation while the + # webhook is unavailable, set the policy to `"Fail"`. See + # [Failure Policy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#failure-policy). failurePolicy: Ignore - # matchPolicy specifies the approach to accepting changes based on the rules of - # the MutatingWebhookConfiguration. - # See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-matchpolicy - # for more details. - # + # Specifies the approach to accepting changes based on the rules of the + # MutatingWebhookConfiguration. See + # [Match Policy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-matchpolicy). matchPolicy: Exact - # timeoutSeconds is the amount of seconds before the webhook request will be ignored - # or fails. - # If it is ignored or fails depends on the failurePolicy - # See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#timeouts - # for more details. - # + # Specifies the number of seconds before the webhook request will be ignored + # or fails. If it is ignored or fails depends on the `failurePolicy`. See + # [timeouts](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#timeouts). timeoutSeconds: 30 - # namespaceSelector is the selector for restricting the webhook to only - # specific namespaces. - # See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-namespaceselector - # for more details. - # Example: + # The selector used by the admission webhook controller to limit what + # namespaces where injection can happen. If unset, all non-system namespaces + # are eligible for injection. See + # [Matching requests: namespace selector](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-namespaceselector). + # + # ```yaml # namespaceSelector: - # matchLabels: - # sidecar-injector: enabled + # matchLabels: + # sidecar-injector: enabled + # ``` + # @type: object + # @default: {} namespaceSelector: {} - # objectSelector is the selector for restricting the webhook to only - # specific labels. - # See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-objectselector - # for more details. - # Example: + # The selector used by the admission webhook controller to limit what objects + # can be affected by mutation. See + # [Matching requests: object selector](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-objectselector). + # + # ```yaml # objectSelector: - # matchLabels: - # vault-sidecar-injector: enabled + # matchLabels: + # sidecar-injector: enabled + # ``` + # @type: object + # @default: {} objectSelector: | matchExpressions: - key: app.kubernetes.io/name @@ -191,95 +264,91 @@ injector: values: - {{ template "vault.name" . }}-agent-injector - # Extra annotations to attach to the webhook + # Defines additional annotations to attach to the webhook. This can either be + # YAML or a YAML-formatted multi-line templated string. + # @type: string or object + # @default: {} annotations: {} - # Deprecated: please use 'webhook.failurePolicy' instead - # Configures failurePolicy of the webhook. The "unspecified" default behaviour depends on the - # API Version of the WebHook. - # To block pod creation while webhook is unavailable, set the policy to `Fail` below. - # See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#failure-policy - # - failurePolicy: Ignore - - # Deprecated: please use 'webhook.namespaceSelector' instead - # namespaceSelector is the selector for restricting the webhook to only - # specific namespaces. - # See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-namespaceselector - # for more details. - # Example: - # namespaceSelector: - # matchLabels: - # sidecar-injector: enabled + # Deprecated: please use + # [`webhook.namespaceSelector`](https://developer.hashicorp.com/vault/docs/platform/k8s/helm/configuration#namespaceselector) instead. + # @type: dictionary + # @default: {} namespaceSelector: {} - # Deprecated: please use 'webhook.objectSelector' instead - # objectSelector is the selector for restricting the webhook to only - # specific labels. - # See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-objectselector - # for more details. - # Example: - # objectSelector: - # matchLabels: - # vault-sidecar-injector: enabled + # Deprecated: please use + # [`webhook.objectSelector`](https://developer.hashicorp.com/vault/docs/platform/k8s/helm/configuration#objectselector) instead. + # @type: dictionary + # @default: {} objectSelector: {} - # Deprecated: please use 'webhook.annotations' instead - # Extra annotations to attach to the webhook - webhookAnnotations: {} + # This value defines additional labels for Vault Agent Injector pods. + # + # ```yaml + # extraLabels: + # 'sample/label1': 'foo' + # 'sample/label2': 'bar' + # ``` + # @type: dictionary + # @default: {} + extraLabels: {} + # The certs section configures how the webhook TLS certs are configured. These + # are the TLS certs for the Kube apiserver communicating to the webhook. By + # default, the injector will generate and manage its own certs, but this + # requires the ability for the injector to update its own + # `MutatingWebhookConfiguration`. In a production environment, custom certs + # should probably be used. Configure the values below to enable this. certs: - # secretName is the name of the secret that has the TLS certificate and - # private key to serve the injector webhook. If this is null, then the - # injector will default to its automatic management mode that will assign - # a service account to the injector to generate its own certificates. + # secretName is the name of the Kubernetes secret that has the TLS certificate + # and private key to serve the injector webhook. If this is null, then the + # injector will default to its automatic management mode. secretName: null - # caBundle is a base64-encoded PEM-encoded certificate bundle for the CA - # that signed the TLS certificate that the webhook serves. This must be set - # if secretName is non-null unless an external service like cert-manager is - # keeping the caBundle updated. + # The PEM-encoded CA public certificate bundle for the TLS certificate served + # by the injector. This must be specified as a string and can't come from a + # secret because it must be statically configured on the Kubernetes + # `MutatingAdmissionWebhook` resource. This only needs to be specified if + # `secretName` is not null. caBundle: "" - # certName and keyName are the names of the files within the secret for - # the TLS cert and private key, respectively. These have reasonable - # defaults but can be customized if necessary. + # The name of the certificate file within the `secretName` secret. certName: tls.crt + # The name of the key file within the `secretName` secret. keyName: tls.key - # Security context for the pod template and the injector container - # The default pod securityContext is: - # runAsNonRoot: true - # runAsGroup: {{ .Values.injector.gid | default 1000 }} - # runAsUser: {{ .Values.injector.uid | default 100 }} - # fsGroup: {{ .Values.injector.gid | default 1000 }} - # and for container is - # allowPrivilegeEscalation: false - # capabilities: - # drop: - # - ALL - securityContext: - pod: {} - container: {} - - resources: {} - # resources: - # requests: - # memory: 256Mi - # cpu: 250m - # limits: - # memory: 256Mi - # cpu: 250m - - # extraEnvironmentVars is a list of extra environment variables to set in the - # injector deployment. + # Extra environment variables to set in the injector deployment. + # + # ```yaml + # # Example setting injector TLS options in a deployment: + # extraEnvironmentVars: + # AGENT_INJECT_TLS_MIN_VERSION: tls13 + # AGENT_INJECT_TLS_CIPHER_SUITES: ... + # ``` + # @type: dictionary + # @default: {} extraEnvironmentVars: {} - # KUBERNETES_SERVICE_HOST: kubernetes.default.svc - # Affinity Settings for injector pods - # This can either be a multi-line string or YAML matching the PodSpec's affinity field. - # Commenting out or setting as empty the affinity variable, will allow - # deployment of multiple replicas to single node services such as Minikube. + # This value defines the [affinity](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) + # for Vault Agent Injector pods. This can either be multi-line string or YAML + # matching the PodSpec's affinity field. It defaults to allowing only a single + # pod on each node, which minimizes risk of the cluster becoming unusable if + # a node is lost. If you need to run more pods per node (for example, testing + # on Minikube), set this value to `null`. + # + # ```yaml + # # Recommended default server affinity: + # affinity: | + # podAntiAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: + # - labelSelector: + # matchLabels: + # app.kubernetes.io/name: {{ template "vault.name" . }}-agent-injector + # app.kubernetes.io/instance: "{{ .Release.Name }}" + # component: webhook + # topologyKey: kubernetes.io/hostname + # ``` + # @type: string or dictionary affinity: | podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: @@ -290,64 +359,164 @@ injector: component: webhook topologyKey: kubernetes.io/hostname - # Topology settings for injector pods - # ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ - # This should be either a multi-line string or YAML matching the topologySpreadConstraints array - # in a PodSpec. + # [Topology settings](https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) + # for injector pods. This can either be YAML or a YAML-formatted multi-line + # templated string. topologySpreadConstraints: [] - # Toleration Settings for injector pods - # This should be either a multi-line string or YAML matching the Toleration array - # in a PodSpec. + # Toleration Settings for injector pods. This should be either a multi-line + # string or YAML matching the Toleration array. tolerations: [] - # nodeSelector labels for server pod assignment, formatted as a multi-line string or YAML map. - # ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector - # Example: + # The nodeSelector labels for injector pod assignment, formatted as a multi-line + # string or YAML map. See https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector + # + # ```yaml # nodeSelector: # beta.kubernetes.io/arch: amd64 + # ``` + # @type: dictionary + # @default: {} nodeSelector: {} - # Priority class for injector pods + # Priority class for injector pods. priorityClassName: "" - # Extra annotations to attach to the injector pods - # This can either be YAML or a YAML-formatted multi-line templated string map - # of the annotations to apply to the injector pods + # This value defines additional annotations for injector pods. This can either + # be YAML or a YAML-formatted multi-line templated string. + # + # ```yaml + # annotations: + # "sample/annotation1": "foo" + # "sample/annotation2": "bar" + # # or + # annotations: | + # "sample/annotation1": "foo" + # "sample/annotation2": "bar" + # ``` + # @type: dictionary + # @default: {} annotations: {} - # Extra labels to attach to the agent-injector - # This should be a YAML map of the labels to apply to the injector - extraLabels: {} + # Deprecated: please use [`webhook.failurePolicy`](https://developer.hashicorp.com/vault/docs/platform/k8s/helm/configuration#failurepolicy) instead. + failurePolicy: Ignore - # Should the injector pods run on the host network (useful when using - # an alternate CNI in EKS) + # Deprecated: please use [`webhook.annotations`](https://developer.hashicorp.com/vault/docs/platform/k8s/helm/configuration#annotations-1) instead. + # @type: dictionary + # @default: {} + webhookAnnotations: {} + + # The service section configures the Kubernetes service for the Vault Agent + # Injector. + service: + # This value defines additional annotations to add to the Vault Agent Injector + # service. This can either be YAML or a YAML-formatted multi-line templated string. + # + # ```yaml + # annotations: + # "sample/annotation1": "foo" + # "sample/annotation2": "bar" + # # or + # annotations: | + # "sample/annotation1": "foo" + # "sample/annotation2": "bar" + # ``` + # @type: dictionary + # @default: {} + annotations: {} + + # Injector serviceAccount specific config. + serviceAccount: + # Extra annotations to attach to the injector serviceAccount. This can either + # be YAML or a YAML-formatted multi-line templated string. + # @type: dictionary + # @default: {} + annotations: {} + + # When set to true, configures the Vault Agent Injector to run on the host + # network. This is useful when alternative cluster networking is used. hostNetwork: false - # Injector service specific config - service: - # Extra annotations to attach to the injector service - annotations: {} - - # Injector serviceAccount specific config - serviceAccount: - # Extra annotations to attach to the injector serviceAccount - annotations: {} + # Configures the port the Vault Agent Injector listens on. + port: 8080 # A disruption budget limits the number of pods of a replicated application - # that are down simultaneously from voluntary disruptions - podDisruptionBudget: {} + # that are down simultaneously from voluntary disruptions. + # + # ```yaml # podDisruptionBudget: # maxUnavailable: 1 + # ``` + # @type: dictionary + # @default: {} + podDisruptionBudget: {} - # strategy for updating the deployment. This can be a multi-line string or a + # Strategy for updating the deployment. This can be a multi-line string or a # YAML map. - strategy: {} + # + # ```yaml + # strategy: + # rollingUpdate: + # maxSurge: 25% + # maxUnavailable: 25% + # type: RollingUpdate + # # or # strategy: | # rollingUpdate: # maxSurge: 25% # maxUnavailable: 25% # type: RollingUpdate + # ``` + # @type: dictionary + # @default: {} + strategy: {} + + # Values that configure the liveness probe for the injector. + livenessProbe: + # When set to a value, configures how many probe failures will be tolerated + # by Kubernetes. + failureThreshold: 2 + # Sets the initial delay of the liveness probe when the container starts. + initialDelaySeconds: 5 + # When set to a value, configures how often (in seconds) to perform the probe. + periodSeconds: 2 + # When set to a value, configures the minimum consecutive successes for the + # probe to be considered successful after having failed. + successThreshold: 1 + # When set to a value, configures the number of seconds after which the probe + # times out. + timeoutSeconds: 5 + # Values that configure the readiness probe for the injector. + readinessProbe: + # When set to a value, configures how many probe failures will be tolerated + # by Kubernetes. + failureThreshold: 2 + # Sets the initial delay of the readiness probe when the container starts. + initialDelaySeconds: 5 + # When set to a value, configures how often (in seconds) to perform the probe. + periodSeconds: 2 + # When set to a value, configures the minimum consecutive successes for the probe + # to be considered successful after having failed. + successThreshold: 1 + # When set to a value, configures the number of seconds after which the probe + # times out. + timeoutSeconds: 5 + # Values that configure the startup probe for the injector. + startupProbe: + # When set to a value, configures how many probe failures will be tolerated + # by Kubernetes. + failureThreshold: 12 + # Sets the initial delay of the startup probe when the container starts. + initialDelaySeconds: 5 + # When set to a value, configures how often (in seconds) to perform the probe. + periodSeconds: 5 + # When set to a value, configures the minimum consecutive successes for the probe + # to be considered successful after having failed. + successThreshold: 1 + # When set to a value, configures the number of seconds after which the probe + # times out. + timeoutSeconds: 5 + server: # If true, or "-" with global.enabled true, Vault server will be installed.