Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -765,7 +765,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 @@ -136,7 +136,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 @@ -41,6 +41,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