diff --git a/docs/api-references/docs.html b/docs/api-references/docs.html index 31780b56a00..a89f209fa5f 100644 --- a/docs/api-references/docs.html +++ b/docs/api-references/docs.html @@ -2606,6 +2606,34 @@

ComponentSpec Optional: Defaults to cluster-level setting

+ + +env
+ + +[]Kubernetes core/v1.EnvVar + + + + +(Optional) +

List of environment variables to set in the container, like +v1.Container.Env. +Note that following env names cannot be used and may be overrided by +tidb-operator built envs. +- NAMESPACE +- TZ +- SERVICE_NAME +- PEER_SERVICE_NAME +- HEADLESS_SERVICE_NAME +- SET_NAME +- HOSTNAME +- CLUSTER_NAME +- POD_NAME +- BINLOG_ENABLED +- SLOW_LOG_FILE

+ +

ConfigUpdateStrategy diff --git a/manifests/crd.yaml b/manifests/crd.yaml index fc7f0e89930..f30ef00f709 100644 --- a/manifests/crd.yaml +++ b/manifests/crd.yaml @@ -1700,6 +1700,102 @@ spec: cluster-level updateStrategy if present Optional: Defaults to cluster-level setting' type: string + env: + description: List of environment variables to set in the container, + like v1.Container.Env. Note that following env names cannot be + used and may be overrided by tidb-operator built envs. - NAMESPACE + - TZ - SERVICE_NAME - PEER_SERVICE_NAME - HEADLESS_SERVICE_NAME + - SET_NAME - HOSTNAME - CLUSTER_NAME - POD_NAME - BINLOG_ENABLED + - SLOW_LOG_FILE + 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 previous 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. The $(VAR_NAME) syntax can be escaped with + a double $$, ie: $$(VAR_NAME). Escaped references will never + be expanded, regardless of whether the variable exists or + not. Defaults to "".' + type: string + valueFrom: + description: EnvVarSource represents a source for the value + of an EnvVar. + properties: + configMapKeyRef: + description: Selects a key from 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' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: ObjectFieldSelector selects an APIVersioned + field of an object. + 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 + resourceFieldRef: + description: ResourceFieldSelector represents container + resources (cpu, memory) and their output format + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: {} + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: SecretKeySelector selects a key of a Secret. + 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' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array hostNetwork: description: 'Whether Hostnetwork of the component is enabled. Override the cluster-level setting if present Optional: Defaults to cluster-level @@ -2558,6 +2654,102 @@ spec: cluster-level updateStrategy if present Optional: Defaults to cluster-level setting' type: string + env: + description: List of environment variables to set in the container, + like v1.Container.Env. Note that following env names cannot be + used and may be overrided by tidb-operator built envs. - NAMESPACE + - TZ - SERVICE_NAME - PEER_SERVICE_NAME - HEADLESS_SERVICE_NAME + - SET_NAME - HOSTNAME - CLUSTER_NAME - POD_NAME - BINLOG_ENABLED + - SLOW_LOG_FILE + 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 previous 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. The $(VAR_NAME) syntax can be escaped with + a double $$, ie: $$(VAR_NAME). Escaped references will never + be expanded, regardless of whether the variable exists or + not. Defaults to "".' + type: string + valueFrom: + description: EnvVarSource represents a source for the value + of an EnvVar. + properties: + configMapKeyRef: + description: Selects a key from 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' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: ObjectFieldSelector selects an APIVersioned + field of an object. + 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 + resourceFieldRef: + description: ResourceFieldSelector represents container + resources (cpu, memory) and their output format + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: {} + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: SecretKeySelector selects a key of a Secret. + 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' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array hostNetwork: description: 'Whether Hostnetwork of the component is enabled. Override the cluster-level setting if present Optional: Defaults to cluster-level @@ -3887,6 +4079,102 @@ spec: description: 'Add --advertise-address to TiDB''s startup parameters Optional: Defaults to false' type: boolean + env: + description: List of environment variables to set in the container, + like v1.Container.Env. Note that following env names cannot be + used and may be overrided by tidb-operator built envs. - NAMESPACE + - TZ - SERVICE_NAME - PEER_SERVICE_NAME - HEADLESS_SERVICE_NAME + - SET_NAME - HOSTNAME - CLUSTER_NAME - POD_NAME - BINLOG_ENABLED + - SLOW_LOG_FILE + 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 previous 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. The $(VAR_NAME) syntax can be escaped with + a double $$, ie: $$(VAR_NAME). Escaped references will never + be expanded, regardless of whether the variable exists or + not. Defaults to "".' + type: string + valueFrom: + description: EnvVarSource represents a source for the value + of an EnvVar. + properties: + configMapKeyRef: + description: Selects a key from 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' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: ObjectFieldSelector selects an APIVersioned + field of an object. + 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 + resourceFieldRef: + description: ResourceFieldSelector represents container + resources (cpu, memory) and their output format + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: {} + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: SecretKeySelector selects a key of a Secret. + 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' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array hostNetwork: description: 'Whether Hostnetwork of the component is enabled. Override the cluster-level setting if present Optional: Defaults to cluster-level @@ -5938,6 +6226,102 @@ spec: cluster-level updateStrategy if present Optional: Defaults to cluster-level setting' type: string + env: + description: List of environment variables to set in the container, + like v1.Container.Env. Note that following env names cannot be + used and may be overrided by tidb-operator built envs. - NAMESPACE + - TZ - SERVICE_NAME - PEER_SERVICE_NAME - HEADLESS_SERVICE_NAME + - SET_NAME - HOSTNAME - CLUSTER_NAME - POD_NAME - BINLOG_ENABLED + - SLOW_LOG_FILE + 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 previous 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. The $(VAR_NAME) syntax can be escaped with + a double $$, ie: $$(VAR_NAME). Escaped references will never + be expanded, regardless of whether the variable exists or + not. Defaults to "".' + type: string + valueFrom: + description: EnvVarSource represents a source for the value + of an EnvVar. + properties: + configMapKeyRef: + description: Selects a key from 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' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: ObjectFieldSelector selects an APIVersioned + field of an object. + 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 + resourceFieldRef: + description: ResourceFieldSelector represents container + resources (cpu, memory) and their output format + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: {} + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: SecretKeySelector selects a key of a Secret. + 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' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array hostNetwork: description: 'Whether Hostnetwork of the component is enabled. Override the cluster-level setting if present Optional: Defaults to cluster-level diff --git a/pkg/apis/pingcap/v1alpha1/openapi_generated.go b/pkg/apis/pingcap/v1alpha1/openapi_generated.go index fb1dda6dfae..ecdab749b9d 100644 --- a/pkg/apis/pingcap/v1alpha1/openapi_generated.go +++ b/pkg/apis/pingcap/v1alpha1/openapi_generated.go @@ -1027,11 +1027,24 @@ func schema_pkg_apis_pingcap_v1alpha1_ComponentSpec(ref common.ReferenceCallback Format: "", }, }, + "env": { + SchemaProps: spec.SchemaProps{ + Description: "List of environment variables to set in the container, like v1.Container.Env. Note that following env names cannot be used and may be overrided by tidb-operator built envs. - NAMESPACE - TZ - SERVICE_NAME - PEER_SERVICE_NAME - HEADLESS_SERVICE_NAME - SET_NAME - HOSTNAME - CLUSTER_NAME - POD_NAME - BINLOG_ENABLED - SLOW_LOG_FILE", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/api/core/v1.EnvVar"), + }, + }, + }, + }, + }, }, }, }, Dependencies: []string{ - "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration"}, + "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration"}, } } @@ -2290,6 +2303,19 @@ func schema_pkg_apis_pingcap_v1alpha1_PDSpec(ref common.ReferenceCallback) commo Format: "", }, }, + "env": { + SchemaProps: spec.SchemaProps{ + Description: "List of environment variables to set in the container, like v1.Container.Env. Note that following env names cannot be used and may be overrided by tidb-operator built envs. - NAMESPACE - TZ - SERVICE_NAME - PEER_SERVICE_NAME - HEADLESS_SERVICE_NAME - SET_NAME - HOSTNAME - CLUSTER_NAME - POD_NAME - BINLOG_ENABLED - SLOW_LOG_FILE", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/api/core/v1.EnvVar"), + }, + }, + }, + }, + }, "limits": { SchemaProps: spec.SchemaProps{ Description: "Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/", @@ -2356,7 +2382,7 @@ func schema_pkg_apis_pingcap_v1alpha1_PDSpec(ref common.ReferenceCallback) commo }, }, Dependencies: []string{ - "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.PDConfig", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.ServiceSpec", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.PDConfig", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.ServiceSpec", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, } } @@ -2743,6 +2769,19 @@ func schema_pkg_apis_pingcap_v1alpha1_PumpSpec(ref common.ReferenceCallback) com Format: "", }, }, + "env": { + SchemaProps: spec.SchemaProps{ + Description: "List of environment variables to set in the container, like v1.Container.Env. Note that following env names cannot be used and may be overrided by tidb-operator built envs. - NAMESPACE - TZ - SERVICE_NAME - PEER_SERVICE_NAME - HEADLESS_SERVICE_NAME - SET_NAME - HOSTNAME - CLUSTER_NAME - POD_NAME - BINLOG_ENABLED - SLOW_LOG_FILE", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/api/core/v1.EnvVar"), + }, + }, + }, + }, + }, "limits": { SchemaProps: spec.SchemaProps{ Description: "Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/", @@ -2811,7 +2850,7 @@ func schema_pkg_apis_pingcap_v1alpha1_PumpSpec(ref common.ReferenceCallback) com }, }, Dependencies: []string{ - "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, } } @@ -3780,6 +3819,19 @@ func schema_pkg_apis_pingcap_v1alpha1_TiDBSpec(ref common.ReferenceCallback) com Format: "", }, }, + "env": { + SchemaProps: spec.SchemaProps{ + Description: "List of environment variables to set in the container, like v1.Container.Env. Note that following env names cannot be used and may be overrided by tidb-operator built envs. - NAMESPACE - TZ - SERVICE_NAME - PEER_SERVICE_NAME - HEADLESS_SERVICE_NAME - SET_NAME - HOSTNAME - CLUSTER_NAME - POD_NAME - BINLOG_ENABLED - SLOW_LOG_FILE", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/api/core/v1.EnvVar"), + }, + }, + }, + }, + }, "limits": { SchemaProps: spec.SchemaProps{ Description: "Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/", @@ -3893,7 +3945,7 @@ func schema_pkg_apis_pingcap_v1alpha1_TiDBSpec(ref common.ReferenceCallback) com }, }, Dependencies: []string{ - "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBConfig", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBServiceSpec", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBSlowLogTailerSpec", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBTLSClient", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBConfig", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBServiceSpec", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBSlowLogTailerSpec", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBTLSClient", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, } } @@ -5640,6 +5692,19 @@ func schema_pkg_apis_pingcap_v1alpha1_TiKVSpec(ref common.ReferenceCallback) com Format: "", }, }, + "env": { + SchemaProps: spec.SchemaProps{ + Description: "List of environment variables to set in the container, like v1.Container.Env. Note that following env names cannot be used and may be overrided by tidb-operator built envs. - NAMESPACE - TZ - SERVICE_NAME - PEER_SERVICE_NAME - HEADLESS_SERVICE_NAME - SET_NAME - HOSTNAME - CLUSTER_NAME - POD_NAME - BINLOG_ENABLED - SLOW_LOG_FILE", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/api/core/v1.EnvVar"), + }, + }, + }, + }, + }, "limits": { SchemaProps: spec.SchemaProps{ Description: "Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/", @@ -5721,7 +5786,7 @@ func schema_pkg_apis_pingcap_v1alpha1_TiKVSpec(ref common.ReferenceCallback) com }, }, Dependencies: []string{ - "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiKVConfig", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiKVConfig", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, } } diff --git a/pkg/apis/pingcap/v1alpha1/tidbcluster_component.go b/pkg/apis/pingcap/v1alpha1/tidbcluster_component.go index 8bd0c4e37dd..f8d6d626379 100644 --- a/pkg/apis/pingcap/v1alpha1/tidbcluster_component.go +++ b/pkg/apis/pingcap/v1alpha1/tidbcluster_component.go @@ -36,6 +36,7 @@ type ComponentAccessor interface { DnsPolicy() corev1.DNSPolicy ConfigUpdateStrategy() ConfigUpdateStrategy BuildPodSpec() corev1.PodSpec + Env() []corev1.EnvVar } type componentAccessorImpl struct { @@ -160,6 +161,10 @@ func (a *componentAccessorImpl) BuildPodSpec() corev1.PodSpec { return spec } +func (a *componentAccessorImpl) Env() []corev1.EnvVar { + return a.ComponentSpec.Env +} + // BaseTiDBSpec returns the base spec of TiDB servers func (tc *TidbCluster) BaseTiDBSpec() ComponentAccessor { return &componentAccessorImpl{&tc.Spec, &tc.Spec.TiDB.ComponentSpec} diff --git a/pkg/apis/pingcap/v1alpha1/types.go b/pkg/apis/pingcap/v1alpha1/types.go index 5992a2b4179..008769a6d88 100644 --- a/pkg/apis/pingcap/v1alpha1/types.go +++ b/pkg/apis/pingcap/v1alpha1/types.go @@ -455,6 +455,24 @@ type ComponentSpec struct { // Optional: Defaults to cluster-level setting // +optional ConfigUpdateStrategy *ConfigUpdateStrategy `json:"configUpdateStrategy,omitempty"` + + // List of environment variables to set in the container, like + // v1.Container.Env. + // Note that following env names cannot be used and may be overrided by + // tidb-operator built envs. + // - NAMESPACE + // - TZ + // - SERVICE_NAME + // - PEER_SERVICE_NAME + // - HEADLESS_SERVICE_NAME + // - SET_NAME + // - HOSTNAME + // - CLUSTER_NAME + // - POD_NAME + // - BINLOG_ENABLED + // - SLOW_LOG_FILE + // +optional + Env []corev1.EnvVar `json:"env,omitempty"` } // +k8s:openapi-gen=true diff --git a/pkg/apis/pingcap/v1alpha1/validation/validation.go b/pkg/apis/pingcap/v1alpha1/validation/validation.go index 39855db7a20..48391b9aa29 100644 --- a/pkg/apis/pingcap/v1alpha1/validation/validation.go +++ b/pkg/apis/pingcap/v1alpha1/validation/validation.go @@ -20,7 +20,9 @@ import ( "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1" "github.com/pingcap/tidb-operator/pkg/label" + corev1 "k8s.io/api/core/v1" apivalidation "k8s.io/apimachinery/pkg/api/validation" + "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" ) @@ -31,10 +33,151 @@ func ValidateTidbCluster(tc *v1alpha1.TidbCluster) field.ErrorList { // validate metadata fldPath := field.NewPath("metadata") // validate metadata/annotations - allErrs = append(allErrs, apivalidation.ValidateAnnotations(tc.ObjectMeta.Annotations, fldPath.Child("annotations"))...) + allErrs = append(allErrs, validateAnnotations(tc.ObjectMeta.Annotations, fldPath.Child("annotations"))...) + // validate spec + allErrs = append(allErrs, validateTiDBClusterSpec(&tc.Spec, field.NewPath("spec"))...) + return allErrs +} + +func validateAnnotations(anns map[string]string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, apivalidation.ValidateAnnotations(anns, fldPath)...) for _, key := range []string{label.AnnPDDeleteSlots, label.AnnTiDBDeleteSlots, label.AnnTiKVDeleteSlots} { - allErrs = append(allErrs, validateDeleteSlots(tc.ObjectMeta.Annotations, key, fldPath.Child("annotations", key))...) + allErrs = append(allErrs, validateDeleteSlots(anns, key, fldPath.Child(key))...) + } + return allErrs +} + +func validateTiDBClusterSpec(spec *v1alpha1.TidbClusterSpec, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, validatePDSpec(&spec.PD, fldPath.Child("pd"))...) + allErrs = append(allErrs, validateTiKVSpec(&spec.TiKV, fldPath.Child("tikv"))...) + allErrs = append(allErrs, validateTiDBSpec(&spec.TiDB, fldPath.Child("tidb"))...) + if spec.Pump != nil { + allErrs = append(allErrs, validatePumpSpec(spec.Pump, fldPath.Child("pump"))...) + } + return allErrs +} + +func validatePDSpec(spec *v1alpha1.PDSpec, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, validateComponentSpec(&spec.ComponentSpec, fldPath)...) + return allErrs +} + +func validateTiKVSpec(spec *v1alpha1.TiKVSpec, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, validateComponentSpec(&spec.ComponentSpec, fldPath)...) + return allErrs +} + +func validateTiDBSpec(spec *v1alpha1.TiDBSpec, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, validateComponentSpec(&spec.ComponentSpec, fldPath)...) + return allErrs +} + +func validatePumpSpec(spec *v1alpha1.PumpSpec, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, validateComponentSpec(&spec.ComponentSpec, fldPath)...) + return allErrs +} + +func validateComponentSpec(spec *v1alpha1.ComponentSpec, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + // TODO validate other fields + allErrs = append(allErrs, validateEnv(spec.Env, fldPath.Child("env"))...) + return allErrs +} + +// validateEnv validates env vars +func validateEnv(vars []corev1.EnvVar, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + for i, ev := range vars { + idxPath := fldPath.Index(i) + if len(ev.Name) == 0 { + allErrs = append(allErrs, field.Required(idxPath.Child("name"), "")) + } else { + for _, msg := range validation.IsEnvVarName(ev.Name) { + allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), ev.Name, msg)) + } + } + allErrs = append(allErrs, validateEnvVarValueFrom(ev, idxPath.Child("valueFrom"))...) + } + return allErrs +} + +func validateEnvVarValueFrom(ev corev1.EnvVar, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if ev.ValueFrom == nil { + return allErrs + } + + numSources := 0 + + if ev.ValueFrom.FieldRef != nil { + numSources++ + allErrs = append(allErrs, field.Invalid(fldPath.Child("fieldRef"), "", "fieldRef is not supported")) + } + if ev.ValueFrom.ResourceFieldRef != nil { + numSources++ + allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceFieldRef"), "", "resourceFieldRef is not supported")) + } + if ev.ValueFrom.ConfigMapKeyRef != nil { + numSources++ + allErrs = append(allErrs, validateConfigMapKeySelector(ev.ValueFrom.ConfigMapKeyRef, fldPath.Child("configMapKeyRef"))...) + } + if ev.ValueFrom.SecretKeyRef != nil { + numSources++ + allErrs = append(allErrs, validateSecretKeySelector(ev.ValueFrom.SecretKeyRef, fldPath.Child("secretKeyRef"))...) + } + + if numSources == 0 { + allErrs = append(allErrs, field.Invalid(fldPath, "", "must specify one of: `configMapKeyRef` or `secretKeyRef`")) + } else if len(ev.Value) != 0 { + if numSources != 0 { + allErrs = append(allErrs, field.Invalid(fldPath, "", "may not be specified when `value` is not empty")) + } + } else if numSources > 1 { + allErrs = append(allErrs, field.Invalid(fldPath, "", "may not have more than one field specified at a time")) + } + + return allErrs +} + +func validateConfigMapKeySelector(s *corev1.ConfigMapKeySelector, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + for _, msg := range apivalidation.NameIsDNSSubdomain(s.Name, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), s.Name, msg)) + } + if len(s.Key) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("key"), "")) + } else { + for _, msg := range validation.IsConfigMapKey(s.Key) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), s.Key, msg)) + } + } + + return allErrs +} + +func validateSecretKeySelector(s *corev1.SecretKeySelector, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + for _, msg := range apivalidation.NameIsDNSSubdomain(s.Name, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), s.Name, msg)) + } + if len(s.Key) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("key"), "")) + } else { + for _, msg := range validation.IsConfigMapKey(s.Key) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), s.Key, msg)) + } } + return allErrs } diff --git a/pkg/apis/pingcap/v1alpha1/validation/validation_test.go b/pkg/apis/pingcap/v1alpha1/validation/validation_test.go index fccae350145..1324d7bbecd 100644 --- a/pkg/apis/pingcap/v1alpha1/validation/validation_test.go +++ b/pkg/apis/pingcap/v1alpha1/validation/validation_test.go @@ -23,8 +23,7 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" ) -// TODO: more UTs -func TestValidateDeletedSlots(t *testing.T) { +func TestValidateAnnotations(t *testing.T) { successCases := []struct { name string tc v1alpha1.TidbCluster @@ -83,7 +82,7 @@ func TestValidateDeletedSlots(t *testing.T) { } for _, v := range successCases { - if errs := ValidateTidbCluster(&v.tc); len(errs) != 0 { + if errs := validateAnnotations(v.tc.ObjectMeta.Annotations, field.NewPath("metadata", "annotations")); len(errs) != 0 { t.Errorf("[%s]: unexpected error: %v", v.name, errs) } } @@ -160,7 +159,7 @@ func TestValidateDeletedSlots(t *testing.T) { } for _, v := range errorCases { - errs := ValidateTidbCluster(&v.tc) + errs := validateAnnotations(v.tc.ObjectMeta.Annotations, field.NewPath("metadata", "annotations")) if len(errs) != len(v.errs) { t.Errorf("[%s]: expected %d failures, got %d failures: %v", v.name, len(v.errs), len(errs), errs) continue diff --git a/pkg/apis/pingcap/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/pingcap/v1alpha1/zz_generated.deepcopy.go index 6bc97a3328c..b24b675f3ac 100644 --- a/pkg/apis/pingcap/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/pingcap/v1alpha1/zz_generated.deepcopy.go @@ -494,6 +494,13 @@ func (in *ComponentSpec) DeepCopyInto(out *ComponentSpec) { *out = new(ConfigUpdateStrategy) **out = **in } + if in.Env != nil { + in, out := &in.Env, &out.Env + *out = make([]v1.EnvVar, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } diff --git a/pkg/manager/member/pd_member_manager.go b/pkg/manager/member/pd_member_manager.go index 12cc2f256cb..75dd9cd46be 100644 --- a/pkg/manager/member/pd_member_manager.go +++ b/pkg/manager/member/pd_member_manager.go @@ -601,7 +601,7 @@ func getNewPDSetForTidbCluster(tc *v1alpha1.TidbCluster, cm *corev1.ConfigMap) ( }, }) } - pdContainer.Env = env + pdContainer.Env = util.MergeEnv(basePDSpec.Env(), env) podSpec.Volumes = vols podSpec.Containers = []corev1.Container{pdContainer} diff --git a/pkg/manager/member/pd_member_manager_test.go b/pkg/manager/member/pd_member_manager_test.go index 829e7791e14..b6147de14bc 100644 --- a/pkg/manager/member/pd_member_manager_test.go +++ b/pkg/manager/member/pd_member_manager_test.go @@ -918,6 +918,20 @@ func testAnnotations(t *testing.T, annotations map[string]string) func(sts *apps } } +func testPDContainerEnv(t *testing.T, env []corev1.EnvVar) func(sts *apps.StatefulSet) { + return func(sts *apps.StatefulSet) { + got := []corev1.EnvVar{} + for _, c := range sts.Spec.Template.Spec.Containers { + if c.Name == v1alpha1.PDMemberType.String() { + got = c.Env + } + } + if diff := cmp.Diff(env, got); diff != "" { + t.Errorf("unexpected (-want, +got): %s", diff) + } + } +} + func TestGetNewPDSetForTidbCluster(t *testing.T) { enable := true tests := []struct { @@ -1035,6 +1049,74 @@ func TestGetNewPDSetForTidbCluster(t *testing.T) { })) }, }, + { + name: "set custom env", + tc: v1alpha1.TidbCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tc", + Namespace: "ns", + }, + Spec: v1alpha1.TidbClusterSpec{ + PD: v1alpha1.PDSpec{ + ComponentSpec: v1alpha1.ComponentSpec{ + Env: []corev1.EnvVar{ + { + Name: "DASHBOARD_SESSION_SECRET", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "dashboard-session-secret", + }, + Key: "encryption_key", + }, + }, + }, + { + Name: "TZ", + Value: "ignored", + }, + }, + }, + }, + }, + }, + testSts: testPDContainerEnv(t, []corev1.EnvVar{ + { + Name: "DASHBOARD_SESSION_SECRET", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "dashboard-session-secret", + }, + Key: "encryption_key", + }, + }, + }, + { + Name: "NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }, + { + Name: "PEER_SERVICE_NAME", + Value: "tc-pd-peer", + }, + { + Name: "SERVICE_NAME", + Value: "tc-pd", + }, + { + Name: "SET_NAME", + Value: "tc-pd", + }, + { + Name: "TZ", + }, + }), + }, // TODO add more tests } diff --git a/pkg/manager/member/pump_member_manager.go b/pkg/manager/member/pump_member_manager.go index 5899c4e8789..995012aa9c9 100644 --- a/pkg/manager/member/pump_member_manager.go +++ b/pkg/manager/member/pump_member_manager.go @@ -344,7 +344,7 @@ func getNewPumpStatefulSet(tc *v1alpha1.TidbCluster, cm *corev1.ConfigMap) (*app ContainerPort: 8250, }}, Resources: controller.ContainerResource(tc.Spec.Pump.ResourceRequirements), - Env: envs, + Env: util.MergeEnv(spec.Env(), envs), VolumeMounts: volumeMounts, }, } diff --git a/pkg/manager/member/tidb_member_manager.go b/pkg/manager/member/tidb_member_manager.go index 3f9da819616..8c89511078d 100644 --- a/pkg/manager/member/tidb_member_manager.go +++ b/pkg/manager/member/tidb_member_manager.go @@ -646,7 +646,7 @@ func getNewTiDBSetForTidbCluster(tc *v1alpha1.TidbCluster, cm *corev1.ConfigMap) }, VolumeMounts: volMounts, Resources: controller.ContainerResource(tc.Spec.TiDB.ResourceRequirements), - Env: envs, + Env: util.MergeEnv(baseTiDBSpec.Env(), envs), ReadinessProbe: &corev1.Probe{ Handler: corev1.Handler{ HTTPGet: &corev1.HTTPGetAction{ diff --git a/pkg/manager/member/tikv_member_manager.go b/pkg/manager/member/tikv_member_manager.go index 56ca06c9a2b..519ae3fa350 100644 --- a/pkg/manager/member/tikv_member_manager.go +++ b/pkg/manager/member/tikv_member_manager.go @@ -443,7 +443,7 @@ func getNewTiKVSetForTidbCluster(tc *v1alpha1.TidbCluster, cm *corev1.ConfigMap) }, }) } - tikvContainer.Env = env + tikvContainer.Env = util.MergeEnv(baseTiKVSpec.Env(), env) podSpec.Volumes = vols podSpec.SecurityContext = podSecurityContext podSpec.InitContainers = initContainers diff --git a/pkg/monitor/monitor/util.go b/pkg/monitor/monitor/util.go index 55851b5e7d0..b213526af7d 100644 --- a/pkg/monitor/monitor/util.go +++ b/pkg/monitor/monitor/util.go @@ -16,13 +16,13 @@ package monitor import ( "encoding/json" "fmt" - "github.com/pingcap/tidb-operator/pkg/util" "sort" "strconv" "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1" "github.com/pingcap/tidb-operator/pkg/controller" "github.com/pingcap/tidb-operator/pkg/label" + "github.com/pingcap/tidb-operator/pkg/util" "github.com/prometheus/prometheus/config" apps "k8s.io/api/apps/v1" core "k8s.io/api/core/v1" @@ -514,7 +514,7 @@ func getMonitorGrafanaContainer(secret *core.Secret, monitor *v1alpha1.TidbMonit if monitor.Spec.Grafana.ImagePullPolicy != nil { c.ImagePullPolicy = *monitor.Spec.Grafana.ImagePullPolicy } - c.Env = sortEnvByName(c.Env) + sort.Sort(util.SortEnvByName(c.Env)) return c } @@ -791,27 +791,3 @@ func getMonitorPVC(monitor *v1alpha1.TidbMonitor) *core.PersistentVolumeClaim { }, } } - -// sortEnvByName in order to avoid syncing same template into different results -func sortEnvByName(envlist []core.EnvVar) []core.EnvVar { - if envlist == nil || len(envlist) < 1 { - return envlist - } - var wrappers EnvListWrapper - wrappers = envlist - sort.Sort(wrappers) - return wrappers -} - -type EnvListWrapper []core.EnvVar - -func (e EnvListWrapper) Len() int { - return len(e) -} -func (e EnvListWrapper) Swap(i, j int) { - e[i], e[j] = e[j], e[i] -} - -func (e EnvListWrapper) Less(i, j int) bool { - return e[i].Name < e[j].Name -} diff --git a/pkg/util/util.go b/pkg/util/util.go index bc02d7ad8db..2d4d1f777e1 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -16,6 +16,7 @@ package util import ( "encoding/json" "fmt" + "sort" "strconv" "strings" @@ -180,3 +181,34 @@ func ClusterTLSSecretName(tcName, component string) string { func TiDBClientTLSSecretName(tcName string) string { return fmt.Sprintf("%s-tidb-client-secret", tcName) } + +// SortEnvByName implements sort.Interface to sort env list by name. +type SortEnvByName []corev1.EnvVar + +func (e SortEnvByName) Len() int { + return len(e) +} +func (e SortEnvByName) Swap(i, j int) { + e[i], e[j] = e[j], e[i] +} + +func (e SortEnvByName) Less(i, j int) bool { + return e[i].Name < e[j].Name +} + +// MergeEnv merges env in `b` to `a` and overrides env that has the same name. +func MergeEnv(a []corev1.EnvVar, b []corev1.EnvVar) []corev1.EnvVar { + tmpEnv := make(map[string]corev1.EnvVar) + for _, e := range a { + tmpEnv[e.Name] = e + } + for _, e := range b { + tmpEnv[e.Name] = e + } + c := make([]corev1.EnvVar, 0) + for _, e := range tmpEnv { + c = append(c, e) + } + sort.Sort(SortEnvByName(c)) + return c +} diff --git a/pkg/util/utils_test.go b/pkg/util/utils_test.go index cccbec683b0..17572c49b87 100644 --- a/pkg/util/utils_test.go +++ b/pkg/util/utils_test.go @@ -16,9 +16,11 @@ package util import ( "testing" + "github.com/google/go-cmp/cmp" . "github.com/onsi/gomega" "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1" "github.com/pingcap/tidb-operator/pkg/label" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" ) @@ -125,3 +127,59 @@ func TestGetPodOrdinals(t *testing.T) { }) } } + +func TestMergeEnv(t *testing.T) { + tests := []struct { + name string + a []corev1.EnvVar + b []corev1.EnvVar + want []corev1.EnvVar + }{ + { + name: "b overrides a with the same name", + a: []corev1.EnvVar{ + { + Name: "foo", + Value: "bar", + }, + { + Name: "xxx", + Value: "xxx", + }, + }, + b: []corev1.EnvVar{ + { + Name: "foo", + Value: "barbar", + }, + { + Name: "new", + Value: "bar", + }, + }, + want: []corev1.EnvVar{ + { + Name: "foo", + Value: "barbar", + }, + { + Name: "new", + Value: "bar", + }, + { + Name: "xxx", + Value: "xxx", + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := MergeEnv(tt.a, tt.b) + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("unwant (-want, +got): %s", diff) + } + }) + } +}