Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down
30 changes: 30 additions & 0 deletions internal/converter/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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"`
Expand Down Expand Up @@ -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"`
Expand Down Expand Up @@ -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"`
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

@cubic-dev-ai cubic-dev-ai Bot Jul 21, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The test coverage for VLogs, VTSingle, VTCluster, VLAgent, VLCollector, and VMAuth with extraVolumes/extraVolumeMounts is not included. Consider adding tests for all affected chart types to match the thorough coverage already provided for VMSingle, VMAgent, VMAlert, VMAnomaly, and VLCluster.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/converter/converter.go, line 1097:

<comment>The test coverage for VLogs, VTSingle, VTCluster, VLAgent, VLCollector, and VMAuth with extraVolumes/extraVolumeMounts is not included. Consider adding tests for all affected chart types to match the thorough coverage already provided for VMSingle, VMAgent, VMAlert, VMAnomaly, and VLCluster.</comment>

<file context>
@@ -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
 
</file context>
Fix with cubic

spec.VolumeMounts = cfg.VolumeMounts

if values.Server.RetentionPeriod != nil {
spec.RetentionPeriod = fmt.Sprint(values.Server.RetentionPeriod)
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down
213 changes: 213 additions & 0 deletions internal/converter/converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down