diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index c81bb0b94..fead943f0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -33,6 +33,7 @@ aliases: * BUGFIX: [helm-converter](https://docs.victoriametrics.com/operator/helm-converter/): fix `securityContext.runAsNonRoot`, `runAsUser`, `runAsGroup`, `seccompProfile`, `appArmorProfile`, `seLinuxOptions`, and `windowsOptions` being silently dropped during conversion; these are now promoted to `spec.securityContext.podSecurityContext`, matching how the operator applies them to containers. See [#2391](https://github.com/VictoriaMetrics/operator/issues/2391). * BUGFIX: [helm-converter](https://docs.victoriametrics.com/operator/helm-converter/): fix TLS/mTLS settings (`tlsCAFile`, `tlsCertFile`, `tlsKeyFile`, `tlsServerName`, `tlsInsecureSkipVerify`) on `remoteWrite` entries being dropped during conversion for vmagent, vlagent, vlcollector, and vmalert charts. See [#2390](https://github.com/VictoriaMetrics/operator/issues/2390). * BUGFIX: [helm-converter](https://docs.victoriametrics.com/operator/helm-converter/): fix `cannot unmarshal object into Go struct field ... headers of type []string` error when a chart's default values.yaml ships a `headers: {}` map (e.g. vmalert's `datasource.headers`, `notifier.headers`, `remoteWrite.headers`); such maps are now normalized to the operator's `key:value` string-slice format before conversion. See [#2398](https://github.com/VictoriaMetrics/operator/issues/2398). +* BUGFIX: [helm-converter](https://docs.victoriametrics.com/operator/helm-converter/): fix `extraVolumes`/`extraVolumeMounts` being silently dropped during conversion for vmsingle, vmagent, vmalert, vmanomaly, vmcluster, vlcluster, vtcluster, vtsingle, vlogs, and vmauth charts. See [#2424](https://github.com/VictoriaMetrics/operator/issues/2424). * BUGFIX: [vmagent](https://docs.victoriametrics.com/operator/resources/vmagent/), [vmanomaly](https://docs.victoriametrics.com/operator/resources/vmanomaly/): target `spec.vpa` at the `VMAgent`/`VMAnomaly` custom resource itself instead of its underlying `Deployment`/`StatefulSet`. VPA rejects a `targetRef` whose owner chain includes another scalable controller, so a `VerticalPodAutoscaler` targeting the workload directly was silently non-functional whenever the CR exposed a `scale` subresource. See [#2415](https://github.com/VictoriaMetrics/operator/issues/2415). * BUGFIX: [vlagent](https://docs.victoriametrics.com/operator/resources/vlagent/): remove a vestigial `scale` subresource declaration referencing nonexistent `spec`/`status` fields, which unconditionally broke `spec.vpa` for `VLAgent` the same way as [#2415](https://github.com/VictoriaMetrics/operator/issues/2415). diff --git a/internal/converter/converter.go b/internal/converter/converter.go index c57adbb5a..62fc8246c 100644 --- a/internal/converter/converter.go +++ b/internal/converter/converter.go @@ -69,6 +69,8 @@ type VMAgentHelmValues struct { ImagePullSecrets []corev1.LocalObjectReference `yaml:"imagePullSecrets,omitempty" json:"imagePullSecrets,omitempty"` ExtraArgs map[string]interface{} `yaml:"extraArgs,omitempty" json:"extraArgs,omitempty"` ExtraEnvs []corev1.EnvVar `yaml:"env,omitempty" json:"env,omitempty"` + ExtraVolumes []corev1.Volume `yaml:"extraVolumes,omitempty" json:"extraVolumes,omitempty"` + ExtraVolumeMounts []corev1.VolumeMount `yaml:"extraVolumeMounts,omitempty" json:"extraVolumeMounts,omitempty"` Resources *corev1.ResourceRequirements `yaml:"resources,omitempty" json:"resources,omitempty"` NodeSelector map[string]string `yaml:"nodeSelector,omitempty" json:"nodeSelector,omitempty"` Tolerations []corev1.Toleration `yaml:"tolerations,omitempty" json:"tolerations,omitempty"` @@ -124,6 +126,8 @@ type VMAnomalyHelmValues struct { ImagePullSecrets []corev1.LocalObjectReference `yaml:"imagePullSecrets,omitempty" json:"imagePullSecrets,omitempty"` ExtraArgs map[string]interface{} `yaml:"extraArgs,omitempty" json:"extraArgs,omitempty"` ExtraEnvs []corev1.EnvVar `yaml:"env,omitempty" json:"env,omitempty"` + ExtraVolumes []corev1.Volume `yaml:"extraVolumes,omitempty" json:"extraVolumes,omitempty"` + ExtraVolumeMounts []corev1.VolumeMount `yaml:"extraVolumeMounts,omitempty" json:"extraVolumeMounts,omitempty"` Resources *corev1.ResourceRequirements `yaml:"resources,omitempty" json:"resources,omitempty"` NodeSelector map[string]string `yaml:"nodeSelector,omitempty" json:"nodeSelector,omitempty"` Tolerations []corev1.Toleration `yaml:"tolerations,omitempty" json:"tolerations,omitempty"` @@ -152,6 +156,8 @@ type VMAlertServerValues struct { ReplicaCount *int32 `yaml:"replicaCount,omitempty" json:"replicaCount,omitempty"` ExtraArgs map[string]interface{} `yaml:"extraArgs,omitempty" json:"extraArgs,omitempty"` ExtraEnvs []corev1.EnvVar `yaml:"env,omitempty" json:"env,omitempty"` + ExtraVolumes []corev1.Volume `yaml:"extraVolumes,omitempty" json:"extraVolumes,omitempty"` + ExtraVolumeMounts []corev1.VolumeMount `yaml:"extraVolumeMounts,omitempty" json:"extraVolumeMounts,omitempty"` Resources *corev1.ResourceRequirements `yaml:"resources,omitempty" json:"resources,omitempty"` NodeSelector map[string]string `yaml:"nodeSelector,omitempty" json:"nodeSelector,omitempty"` Tolerations []corev1.Toleration `yaml:"tolerations,omitempty" json:"tolerations,omitempty"` @@ -255,6 +261,8 @@ type ServerValues struct { RetentionPeriod interface{} `yaml:"retentionPeriod,omitempty" json:"retentionPeriod,omitempty"` ExtraArgs map[string]interface{} `yaml:"extraArgs,omitempty" json:"extraArgs,omitempty"` ExtraEnvs []corev1.EnvVar `yaml:"extraEnvs,omitempty" json:"extraEnvs,omitempty"` + ExtraVolumes []corev1.Volume `yaml:"extraVolumes,omitempty" json:"extraVolumes,omitempty"` + ExtraVolumeMounts []corev1.VolumeMount `yaml:"extraVolumeMounts,omitempty" json:"extraVolumeMounts,omitempty"` Resources *corev1.ResourceRequirements `yaml:"resources,omitempty" json:"resources,omitempty"` NodeSelector map[string]string `yaml:"nodeSelector,omitempty" json:"nodeSelector,omitempty"` Tolerations []corev1.Toleration `yaml:"tolerations,omitempty" json:"tolerations,omitempty"` @@ -866,6 +874,14 @@ func convertCommonConfig(values ServerValues, global GlobalValues) (commonConfig cfg.ExtraEnvs = values.ExtraEnvs } + if len(values.ExtraVolumes) > 0 { + cfg.Volumes = values.ExtraVolumes + } + + if len(values.ExtraVolumeMounts) > 0 { + cfg.VolumeMounts = values.ExtraVolumeMounts + } + storage, err := convertPersistentVolume(values.PersistentVolume) if err != nil { return cfg, err @@ -1078,6 +1094,8 @@ func convertVMSingleSpec(values *VMSingleHelmValues) (*vmv1beta1.VMSingleSpec, e spec.PodMetadata = cfg.PodMetadata spec.ServiceSpec = cfg.ServiceSpec spec.Storage = cfg.Storage + spec.Volumes = cfg.Volumes + spec.VolumeMounts = cfg.VolumeMounts if values.Server.RetentionPeriod != nil { spec.RetentionPeriod = fmt.Sprint(values.Server.RetentionPeriod) @@ -1099,6 +1117,8 @@ func convertVMAnomalySpec(values *VMAnomalyHelmValues) (*vmv1.VMAnomalySpec, err ReplicaCount: values.ReplicaCount, ExtraArgs: values.ExtraArgs, ExtraEnvs: values.ExtraEnvs, + ExtraVolumes: values.ExtraVolumes, + ExtraVolumeMounts: values.ExtraVolumeMounts, Resources: values.Resources, NodeSelector: values.NodeSelector, Tolerations: values.Tolerations, @@ -1116,6 +1136,8 @@ func convertVMAnomalySpec(values *VMAnomalyHelmValues) (*vmv1.VMAnomalySpec, err spec.Image = cfg.Image spec.ExtraArgs = cfg.ExtraArgs spec.ExtraEnvs = cfg.ExtraEnvs + spec.Volumes = cfg.Volumes + spec.VolumeMounts = cfg.VolumeMounts spec.Resources = cfg.Resources spec.NodeSelector = cfg.NodeSelector spec.Tolerations = cfg.Tolerations @@ -1151,6 +1173,8 @@ func convertVMAlertSpec(values *VMAlertHelmValues) (*vmv1beta1.VMAlertSpec, erro ReplicaCount: values.Server.ReplicaCount, ExtraArgs: values.Server.ExtraArgs, ExtraEnvs: values.Server.ExtraEnvs, + ExtraVolumes: values.Server.ExtraVolumes, + ExtraVolumeMounts: values.Server.ExtraVolumeMounts, Resources: values.Server.Resources, NodeSelector: values.Server.NodeSelector, Tolerations: values.Server.Tolerations, @@ -1168,6 +1192,8 @@ func convertVMAlertSpec(values *VMAlertHelmValues) (*vmv1beta1.VMAlertSpec, erro spec.Image = cfg.Image spec.ExtraArgs = cfg.ExtraArgs spec.ExtraEnvs = cfg.ExtraEnvs + spec.Volumes = cfg.Volumes + spec.VolumeMounts = cfg.VolumeMounts spec.Resources = cfg.Resources spec.NodeSelector = cfg.NodeSelector spec.Tolerations = cfg.Tolerations @@ -1198,6 +1224,8 @@ func convertVMAgentSpec(values *VMAgentHelmValues) (*vmv1beta1.VMAgentSpec, erro ReplicaCount: values.ReplicaCount, ExtraArgs: values.ExtraArgs, ExtraEnvs: values.ExtraEnvs, + ExtraVolumes: values.ExtraVolumes, + ExtraVolumeMounts: values.ExtraVolumeMounts, Resources: values.Resources, NodeSelector: values.NodeSelector, Tolerations: values.Tolerations, @@ -1215,6 +1243,8 @@ func convertVMAgentSpec(values *VMAgentHelmValues) (*vmv1beta1.VMAgentSpec, erro spec.Image = cfg.Image spec.ExtraArgs = cfg.ExtraArgs spec.ExtraEnvs = cfg.ExtraEnvs + spec.Volumes = cfg.Volumes + spec.VolumeMounts = cfg.VolumeMounts spec.Resources = cfg.Resources spec.NodeSelector = cfg.NodeSelector spec.Tolerations = cfg.Tolerations diff --git a/internal/converter/converter_test.go b/internal/converter/converter_test.go index 10f26a9dc..e8975d3f5 100644 --- a/internal/converter/converter_test.go +++ b/internal/converter/converter_test.go @@ -374,6 +374,39 @@ func TestConvertVMSingle(t *testing.T) { return cr }, ) + + // extraVolumes/extraVolumeMounts must be propagated. See #2424. + f( + &VMSingleHelmValues{ + Server: ServerValues{ + ExtraVolumes: []corev1.Volume{ + {Name: "extra", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}, + }, + ExtraVolumeMounts: []corev1.VolumeMount{ + {Name: "extra", MountPath: "/extra"}, + }, + }, + }, + func() *vmv1beta1.VMSingle { + cr := &vmv1beta1.VMSingle{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "operator.victoriametrics.com/v1beta1", + Kind: "VMSingle", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-ns", + }, + } + cr.Spec.Volumes = []corev1.Volume{ + {Name: "extra", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}, + } + cr.Spec.VolumeMounts = []corev1.VolumeMount{ + {Name: "extra", MountPath: "/extra"}, + } + return cr + }, + ) } func TestConvertVMCluster(t *testing.T) { @@ -492,6 +525,37 @@ func TestConvertVMAgent(t *testing.T) { return cr }, ) + + // extraVolumes/extraVolumeMounts must be propagated. See #2424. + f( + &VMAgentHelmValues{ + ExtraVolumes: []corev1.Volume{ + {Name: "extra", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}, + }, + ExtraVolumeMounts: []corev1.VolumeMount{ + {Name: "extra", MountPath: "/extra"}, + }, + }, + func() *vmv1beta1.VMAgent { + cr := &vmv1beta1.VMAgent{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "operator.victoriametrics.com/v1beta1", + Kind: "VMAgent", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-agent", + Namespace: "test-ns", + }, + } + cr.Spec.Volumes = []corev1.Volume{ + {Name: "extra", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}, + } + cr.Spec.VolumeMounts = []corev1.VolumeMount{ + {Name: "extra", MountPath: "/extra"}, + } + return cr + }, + ) } func TestConvertVMAlert(t *testing.T) { @@ -535,6 +599,39 @@ func TestConvertVMAlert(t *testing.T) { return cr }, ) + + // extraVolumes/extraVolumeMounts must be propagated. See #2424. + f( + &VMAlertHelmValues{ + Server: VMAlertServerValues{ + ExtraVolumes: []corev1.Volume{ + {Name: "extra", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}, + }, + ExtraVolumeMounts: []corev1.VolumeMount{ + {Name: "extra", MountPath: "/extra"}, + }, + }, + }, + func() *vmv1beta1.VMAlert { + cr := &vmv1beta1.VMAlert{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "operator.victoriametrics.com/v1beta1", + Kind: "VMAlert", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-alert", + Namespace: "test-ns", + }, + } + cr.Spec.Volumes = []corev1.Volume{ + {Name: "extra", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}, + } + cr.Spec.VolumeMounts = []corev1.VolumeMount{ + {Name: "extra", MountPath: "/extra"}, + } + return cr + }, + ) } func TestConvertVMAnomaly(t *testing.T) { @@ -584,6 +681,51 @@ func TestConvertVMAnomaly(t *testing.T) { return cr }, ) + + // extraVolumes/extraVolumeMounts must be propagated. See #2424. + f( + &VMAnomalyHelmValues{ + Reader: &VMAnomalyReaderValues{ + DatasourceURL: "http://vmselect:8481/select/0/prometheus", + SamplingPeriod: "1m", + }, + Writer: &VMAnomalyWriterValues{ + DatasourceURL: "http://vminsert:8480/insert/0/prometheus", + }, + ExtraVolumes: []corev1.Volume{ + {Name: "extra", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}, + }, + ExtraVolumeMounts: []corev1.VolumeMount{ + {Name: "extra", MountPath: "/extra"}, + }, + }, + func() *vmv1.VMAnomaly { + cr := &vmv1.VMAnomaly{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "operator.victoriametrics.com/v1", + Kind: "VMAnomaly", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-anomaly", + Namespace: "test-ns", + }, + } + cr.Spec.Reader = &vmv1.VMAnomalyReadersSpec{ + DatasourceURL: "http://vmselect:8481/select/0/prometheus", + SamplingPeriod: "1m", + } + cr.Spec.Writer = &vmv1.VMAnomalyWritersSpec{ + DatasourceURL: "http://vminsert:8480/insert/0/prometheus", + } + cr.Spec.Volumes = []corev1.Volume{ + {Name: "extra", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}, + } + cr.Spec.VolumeMounts = []corev1.VolumeMount{ + {Name: "extra", MountPath: "/extra"}, + } + return cr + }, + ) } func TestConvertVLAgent(t *testing.T) { f := func(values *VLAgentHelmValues, expected func() *vmv1.VLAgent) { @@ -713,6 +855,77 @@ func TestConvertVLCluster(t *testing.T) { } }, ) + + // extraVolumes/extraVolumeMounts must be propagated for every cluster role. See #2424. + f( + &VLClusterHelmValues{ + VLSelect: ServerValues{ + Enabled: ptr.To(true), + ExtraVolumes: []corev1.Volume{ + {Name: "extra", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}, + }, + ExtraVolumeMounts: []corev1.VolumeMount{ + {Name: "extra", MountPath: "/extra"}, + }, + }, + VLInsert: ServerValues{ + Enabled: ptr.To(true), + ExtraVolumes: []corev1.Volume{ + {Name: "extra", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}, + }, + ExtraVolumeMounts: []corev1.VolumeMount{ + {Name: "extra", MountPath: "/extra"}, + }, + }, + VLStorage: ServerValues{ + Enabled: ptr.To(true), + ExtraVolumes: []corev1.Volume{ + {Name: "extra", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}, + }, + ExtraVolumeMounts: []corev1.VolumeMount{ + {Name: "extra", MountPath: "/extra"}, + }, + }, + }, + func() *vmv1.VLCluster { + extraVolumes := []corev1.Volume{ + {Name: "extra", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}, + } + extraVolumeMounts := []corev1.VolumeMount{ + {Name: "extra", MountPath: "/extra"}, + } + return &vmv1.VLCluster{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "operator.victoriametrics.com/v1", + Kind: "VLCluster", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-name", + Namespace: "test-ns", + }, + Spec: vmv1.VLClusterSpec{ + VLSelect: &vmv1.VLSelect{ + CommonAppsParams: vmv1beta1.CommonAppsParams{ + Volumes: extraVolumes, + VolumeMounts: extraVolumeMounts, + }, + }, + VLInsert: &vmv1.VLInsert{ + CommonAppsParams: vmv1beta1.CommonAppsParams{ + Volumes: extraVolumes, + VolumeMounts: extraVolumeMounts, + }, + }, + VLStorage: &vmv1.VLStorage{ + CommonAppsParams: vmv1beta1.CommonAppsParams{ + Volumes: extraVolumes, + VolumeMounts: extraVolumeMounts, + }, + }, + }, + } + }, + ) } func TestConvertVLCollector(t *testing.T) { f := func(values *VLCollectorHelmValues, expected func() *vmv1.VLAgent) {