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
2 changes: 1 addition & 1 deletion api/operator/v1beta1/vmagent_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ func (cr *VMAgent) ScrapeSelectors(scrape client.Object) (*metav1.LabelSelector,

// IsUnmanaged checks if object should managed any config objects
func (cr *VMAgent) IsUnmanaged(scrape client.Object) bool {
if !cr.DeletionTimestamp.IsZero() || cr.Status.ParsingSpecError != "" {
if !cr.DeletionTimestamp.IsZero() || (cr.Status.ParsingSpecError != "" && !HasUnknownFields(cr.Status.ParsingSpecError)) {
return true
}
if scrape == nil {
Expand Down
19 changes: 19 additions & 0 deletions api/operator/v1beta1/vmagent_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,22 @@ func TestVMAgent_PrefixedName(t *testing.T) {
f("myapp", false, "vmagent-myapp")
f("myapp", true, "myapp")
}

// TestVMAgent_IsUnmanaged is the VMAgent counterpart of TestVMAlert_IsUnmanaged.
func TestVMAgent_IsUnmanaged(t *testing.T) {
f := func(cr VMAgent, want bool) {
t.Helper()
assert.Equal(t, want, cr.IsUnmanaged(nil))
}

f(VMAgent{Spec: VMAgentSpec{CommonScrapeParams: CommonScrapeParams{SelectAllByDefault: true}}}, false)
f(VMAgent{}, true)
f(VMAgent{
Status: VMAgentStatus{ParsingSpecError: `json: unknown field "foo"`},
Spec: VMAgentSpec{CommonScrapeParams: CommonScrapeParams{SelectAllByDefault: true}},
}, false)
f(VMAgent{
Status: VMAgentStatus{ParsingSpecError: "some other unrelated parse failure"},
Spec: VMAgentSpec{CommonScrapeParams: CommonScrapeParams{SelectAllByDefault: true}},
}, true)
}
2 changes: 1 addition & 1 deletion api/operator/v1beta1/vmalert_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ func (cr *VMAlert) AsURL(isExtra bool) string {

// IsUnmanaged checks if object should managed any config objects
func (cr *VMAlert) IsUnmanaged() bool {
if !cr.DeletionTimestamp.IsZero() || cr.Status.ParsingSpecError != "" {
if !cr.DeletionTimestamp.IsZero() || (cr.Status.ParsingSpecError != "" && !HasUnknownFields(cr.Status.ParsingSpecError)) {
return true
}
return !cr.Spec.SelectAllByDefault && cr.Spec.RuleSelector == nil && cr.Spec.RuleNamespaceSelector == nil
Expand Down
22 changes: 22 additions & 0 deletions api/operator/v1beta1/vmalert_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,25 @@ func TestVMAlert_PrefixedName(t *testing.T) {
f("myapp", false, "vmalert-myapp")
f("myapp", true, "myapp")
}

// TestVMAlert_IsUnmanaged: an unknown-fields-only ParsingSpecError (the operator's
// strict-unmarshal warning that every other ParsingSpecError check in the codebase treats
// as non-fatal) must not make an otherwise fully-configured VMAlert unmanaged, or rule
// selection silently never runs.
func TestVMAlert_IsUnmanaged(t *testing.T) {
f := func(cr VMAlert, want bool) {
t.Helper()
assert.Equal(t, want, cr.IsUnmanaged())
}

f(VMAlert{Spec: VMAlertSpec{SelectAllByDefault: true}}, false)
f(VMAlert{}, true)
f(VMAlert{
Status: VMAlertStatus{ParsingSpecError: `json: unknown field "foo"`},
Spec: VMAlertSpec{SelectAllByDefault: true},
}, false)
f(VMAlert{
Status: VMAlertStatus{ParsingSpecError: "some other unrelated parse failure"},
Spec: VMAlertSpec{SelectAllByDefault: true},
}, true)
}
2 changes: 1 addition & 1 deletion api/operator/v1beta1/vmalertmanager_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ func (*VMAlertmanager) ProbeNeedLiveness() bool {

// IsUnmanaged checks if alertmanager should managed any alertmanager config objects
func (cr *VMAlertmanager) IsUnmanaged() bool {
if !cr.DeletionTimestamp.IsZero() || cr.Status.ParsingSpecError != "" {
if !cr.DeletionTimestamp.IsZero() || (cr.Status.ParsingSpecError != "" && !HasUnknownFields(cr.Status.ParsingSpecError)) {
return true
}
return !cr.Spec.SelectAllByDefault && cr.Spec.ConfigSelector == nil && cr.Spec.ConfigNamespaceSelector == nil
Expand Down
19 changes: 19 additions & 0 deletions api/operator/v1beta1/vmalertmanager_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,22 @@ func TestVMAlertmanager_PrefixedName(t *testing.T) {
f("myapp", false, "vmalertmanager-myapp")
f("myapp", true, "myapp")
}

// TestVMAlertmanager_IsUnmanaged is the VMAlertmanager counterpart of TestVMAlert_IsUnmanaged.
func TestVMAlertmanager_IsUnmanaged(t *testing.T) {
f := func(cr VMAlertmanager, want bool) {
t.Helper()
assert.Equal(t, want, cr.IsUnmanaged())
}

f(VMAlertmanager{Spec: VMAlertmanagerSpec{SelectAllByDefault: true}}, false)
f(VMAlertmanager{}, true)
f(VMAlertmanager{
Status: VMAlertmanagerStatus{ParsingSpecError: `json: unknown field "foo"`},
Spec: VMAlertmanagerSpec{SelectAllByDefault: true},
}, false)
f(VMAlertmanager{
Status: VMAlertmanagerStatus{ParsingSpecError: "some other unrelated parse failure"},
Spec: VMAlertmanagerSpec{SelectAllByDefault: true},
}, true)
}
2 changes: 1 addition & 1 deletion api/operator/v1beta1/vmauth_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -772,7 +772,7 @@ func (cr *VMAuth) IsOwnsServiceAccount() bool {

// IsUnmanaged checks if object should managed any config objects
func (cr *VMAuth) IsUnmanaged() bool {
if !cr.DeletionTimestamp.IsZero() || cr.Status.ParsingSpecError != "" {
if !cr.DeletionTimestamp.IsZero() || (cr.Status.ParsingSpecError != "" && !HasUnknownFields(cr.Status.ParsingSpecError)) {
return true
}
return (!cr.Spec.SelectAllByDefault && cr.Spec.UserSelector == nil && cr.Spec.UserNamespaceSelector == nil) ||
Expand Down
19 changes: 19 additions & 0 deletions api/operator/v1beta1/vmauth_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,22 @@ func TestVMAuth_PrefixedName(t *testing.T) {
f("myapp", false, "vmauth-myapp")
f("myapp", true, "myapp")
}

// TestVMAuth_IsUnmanaged is the VMAuth counterpart of TestVMAlert_IsUnmanaged.
func TestVMAuth_IsUnmanaged(t *testing.T) {
f := func(cr VMAuth, want bool) {
t.Helper()
assert.Equal(t, want, cr.IsUnmanaged())
}

f(VMAuth{Spec: VMAuthSpec{SelectAllByDefault: true}}, false)
f(VMAuth{}, true)
f(VMAuth{
Status: VMAuthStatus{ParsingSpecError: `json: unknown field "foo"`},
Spec: VMAuthSpec{SelectAllByDefault: true},
}, false)
f(VMAuth{
Status: VMAuthStatus{ParsingSpecError: "some other unrelated parse failure"},
Spec: VMAuthSpec{SelectAllByDefault: true},
}, true)
}
2 changes: 1 addition & 1 deletion api/operator/v1beta1/vmsingle_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func (cr *VMSingle) ScrapeSelectors(scrape client.Object) (*metav1.LabelSelector

// IsUnmanaged checks if object should managed any config objects
func (cr *VMSingle) IsUnmanaged(scrape client.Object) bool {
if !cr.DeletionTimestamp.IsZero() || cr.Status.ParsingSpecError != "" {
if !cr.DeletionTimestamp.IsZero() || (cr.Status.ParsingSpecError != "" && !HasUnknownFields(cr.Status.ParsingSpecError)) {
return true
}
if scrape == nil {
Expand Down
19 changes: 19 additions & 0 deletions api/operator/v1beta1/vmsingle_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,22 @@ func TestVMSingle_PrefixedName(t *testing.T) {
// useLegacyNaming — CR name used as-is
f("myapp", true, "myapp")
}

// TestVMSingle_IsUnmanaged is the VMSingle counterpart of TestVMAlert_IsUnmanaged.
func TestVMSingle_IsUnmanaged(t *testing.T) {
f := func(cr VMSingle, want bool) {
t.Helper()
assert.Equal(t, want, cr.IsUnmanaged(nil))
}

f(VMSingle{Spec: VMSingleSpec{CommonScrapeParams: CommonScrapeParams{SelectAllByDefault: true}}}, false)
f(VMSingle{}, true)
f(VMSingle{
Status: VMSingleStatus{ParsingSpecError: `json: unknown field "foo"`},
Spec: VMSingleSpec{CommonScrapeParams: CommonScrapeParams{SelectAllByDefault: true}},
}, false)
f(VMSingle{
Status: VMSingleStatus{ParsingSpecError: "some other unrelated parse failure"},
Spec: VMSingleSpec{CommonScrapeParams: CommonScrapeParams{SelectAllByDefault: true}},
}, true)
}
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ aliases:
* 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).
* BUGFIX: [vmalert](https://docs.victoriametrics.com/operator/resources/vmalert/), [vmagent](https://docs.victoriametrics.com/operator/resources/vmagent/), [vmalertmanager](https://docs.victoriametrics.com/operator/resources/vmalertmanager/), [vmauth](https://docs.victoriametrics.com/operator/resources/vmauth/), [vmsingle](https://docs.victoriametrics.com/operator/resources/vmsingle/): fix child object selection (`VMRule`, scrape objects, `VMAlertmanagerConfig`, `VMUser`) being silently skipped whenever the parent CR's spec contains a field the running operator version doesn't recognize (e.g. after a CRD/operator version mismatch), even though the CR otherwise reconciles successfully with no errors. See [#2444](https://github.com/VictoriaMetrics/operator/issues/2444).

## [v0.73.1](https://github.com/VictoriaMetrics/operator/releases/tag/v0.73.1)
**Release date:** 08 July 2026
Expand Down