diff --git a/api/operator/v1beta1/vmextra_types.go b/api/operator/v1beta1/vmextra_types.go index dc9a4e003..85aa2165f 100644 --- a/api/operator/v1beta1/vmextra_types.go +++ b/api/operator/v1beta1/vmextra_types.go @@ -1021,19 +1021,17 @@ func (m *StringOrArray) UnmarshalJSON(data []byte) error { } } -// MarshalJSON implements json.Marshaller interface -func (m *StringOrArray) MarshalJSON() ([]byte, error) { - if m == nil { - return nil, nil - } - ms := *m +// MarshalJSON implements json.Marshaller interface. +// Use a value receiver so nested struct fields of type StringOrArray +// are serialized as strings when they contain a single element. +func (m StringOrArray) MarshalJSON() ([]byte, error) { switch { - case len(ms) == 0: + case len(m) == 0: return json.Marshal("") - case len(ms) == 1: - return json.Marshal(ms[0]) + case len(m) == 1: + return json.Marshal(m[0]) default: - return json.Marshal([]string(ms)) + return json.Marshal([]string(m)) } } diff --git a/api/operator/v1beta1/vmextra_types_test.go b/api/operator/v1beta1/vmextra_types_test.go index 8496eade9..436f02371 100644 --- a/api/operator/v1beta1/vmextra_types_test.go +++ b/api/operator/v1beta1/vmextra_types_test.go @@ -102,6 +102,13 @@ func TestStringOrArrayMarshal(t *testing.T) { `) f(&StringOrArray{}, yaml.Marshal, `"" `) + got, err := json.Marshal(struct { + Match StringOrArray `json:"match"` + }{ + Match: StringOrArray{"1"}, + }) + assert.NoError(t, err) + assert.Equal(t, `{"match":"1"}`, string(got)) } diff --git a/internal/controller/operator/factory/converter/apis_test.go b/internal/controller/operator/factory/converter/apis_test.go index ae29eb192..e4efa561a 100644 --- a/internal/controller/operator/factory/converter/apis_test.go +++ b/internal/controller/operator/factory/converter/apis_test.go @@ -2,6 +2,7 @@ package converter import ( "context" + "encoding/json" "testing" promv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" @@ -224,6 +225,33 @@ func TestServiceMonitor(t *testing.T) { }) } +func TestServiceMonitorSingleRegexMarshalsAsJSONScalar(t *testing.T) { + ctx := context.TODO() + got := ServiceMonitor(ctx, &promv1.ServiceMonitor{ + Spec: promv1.ServiceMonitorSpec{ + Endpoints: []promv1.Endpoint{{ + MetricRelabelConfigs: []promv1.RelabelConfig{{ + Action: "drop", + Regex: "^karpenter_cloudprovider_instance.*", + SourceLabels: []promv1.LabelName{"__name__"}, + }}, + RelabelConfigs: []promv1.RelabelConfig{{ + Action: "replace", + Regex: "^(.*)$", + SourceLabels: []promv1.LabelName{"__meta_kubernetes_pod_node_name"}, + TargetLabel: "nodename", + }}, + }}, + }, + }, &config.BaseOperatorConf{}) + + data, err := json.Marshal(got) + assert.NoError(t, err) + assert.Contains(t, string(data), `"regex":"^karpenter_cloudprovider_instance.*"`) + assert.Contains(t, string(data), `"regex":"^(.*)$"`) + assert.NotContains(t, string(data), `"regex":["`) +} + func TestConvertPodEndpoints(t *testing.T) { type opts struct { pe []promv1.PodMetricsEndpoint