Skip to content

Commit

Permalink
vmalertmanager: ignore content of configSecret if name clashes with d…
Browse files Browse the repository at this point in the history
…efault (#955)

* vmalertmanager: ignore content of configSecret if name clashes with default

Ignores any content from secret define at cr.spec.configSecret, if it name clashes with name of the secret created by operator.

#954

* add test
  • Loading branch information
f41gh7 committed May 20, 2024
1 parent 0829591 commit c8534a5
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 6 deletions.
3 changes: 3 additions & 0 deletions api/victoriametrics/v1beta1/vmalertmanager_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ func (r *VMAlertmanager) SetupWebhookWithManager(mgr ctrl.Manager) error {
var _ webhook.Validator = &VMAlertmanager{}

func (r *VMAlertmanager) sanityCheck() error {
if r.Spec.ConfigSecret == r.ConfigSecretName() {
return fmt.Errorf("cr.spec.configSecret=%q cannot have the same secret name as secret created by operator for storing config. Please change it", r.ConfigSecretName())
}
return nil
}

Expand Down
179 changes: 179 additions & 0 deletions controllers/factory/alertmanager/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ package alertmanager

import (
"context"
"os"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v2"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/ptr"

operatorv1beta1 "github.com/VictoriaMetrics/operator/api/v1beta1"
Expand Down Expand Up @@ -1279,3 +1283,178 @@ authorization:
})
}
}

func Test_UpdateDefaultAMConfig(t *testing.T) {
type args struct {
ctx context.Context
cr *operatorv1beta1.VMAlertmanager
}
tests := []struct {
name string
args args
wantErr bool
predefinedObjects []runtime.Object
secretMustBeMissing bool
}{
{
name: "with alertmanager config support",
args: args{
ctx: context.TODO(),
cr: &operatorv1beta1.VMAlertmanager{
ObjectMeta: metav1.ObjectMeta{
Name: "test-am",
Namespace: "default",
},
Spec: operatorv1beta1.VMAlertmanagerSpec{
ConfigSecret: "vmalertmanager-test-am-config",
ConfigRawYaml: "global: {}",
ConfigSelector: &metav1.LabelSelector{},
ConfigNamespaceSelector: &metav1.LabelSelector{},
SelectAllByDefault: true,
},
},
},
predefinedObjects: []runtime.Object{
&v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "vmalertmanager-test-am-config",
Namespace: "default",
},
Data: map[string][]byte{alertmanagerSecretConfigKey: {}},
},
&operatorv1beta1.VMAlertmanagerConfig{
ObjectMeta: metav1.ObjectMeta{
Name: "test-amc",
Namespace: "default",
},
Spec: operatorv1beta1.VMAlertmanagerConfigSpec{
InhibitRules: []operatorv1beta1.InhibitRule{
{Equal: []string{"alertname"}, SourceMatchers: []string{"severity=\"critical\""}, TargetMatchers: []string{"severity=\"warning\""}},
{SourceMatchers: []string{"alertname=\"QuietWeeklyNotifications\""}, TargetMatchers: []string{"alert_group=\"l2ci_weekly\""}},
},
Route: &operatorv1beta1.Route{
GroupBy: []string{"alertname", "l2ci_channel"},
Receiver: "blackhole",
Routes: []*operatorv1beta1.SubRoute{
{Receiver: "blackhole", Matchers: []string{"alertname=\"QuietWeeklyNotifications\""}},
{Receiver: "blackhole", Matchers: []string{"alertname=\"QuietDailyNotifications\""}},
{Receiver: "l2ci_receiver", Matchers: []string{"alert_group=~\"^l2ci.*\""}},
},
},
Receivers: []operatorv1beta1.Receiver{
{
Name: "l2ci_receiver",
WebhookConfigs: []operatorv1beta1.WebhookConfig{
{URL: ptr.To("http://notification_stub_ci1:8080")},
},
},
{Name: "blackhole"},
{
Name: "ca_em_receiver",
WebhookConfigs: []operatorv1beta1.WebhookConfig{
{URL: ptr.To("http://notification_stub_ci2:8080")},
},
},
},
},
},
},
},
}
assert.Nil(t, os.Setenv("WATCH_NAMESPACE", "default"))
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fclient := k8stools.GetTestClientWithObjects(tt.predefinedObjects)

// Create secret with alert manager config
if err := createDefaultAMConfig(tt.args.ctx, tt.args.cr, fclient); (err != nil) != tt.wantErr {
t.Fatalf("createDefaultAMConfig() error = %v, wantErr %v", err, tt.wantErr)
}
var createdSecret v1.Secret
secretName := tt.args.cr.ConfigSecretName()
err := fclient.Get(tt.args.ctx, types.NamespacedName{Namespace: tt.args.cr.Namespace, Name: secretName}, &createdSecret)
if err != nil {
if errors.IsNotFound(err) && tt.secretMustBeMissing {
return
}
t.Fatalf("config for alertmanager not exist, err: %v", err)
}

// check secret config after creating
d, ok := createdSecret.Data[alertmanagerSecretConfigKey]
if !ok {
t.Fatalf("config for alertmanager not exist, err: %v", err)
}
var secretConfig alertmanagerConfig
err = yaml.Unmarshal(d, &secretConfig)
if err != nil {
t.Fatalf("could not unmarshall secret config data into structure, err: %v", err)
}
var amc operatorv1beta1.VMAlertmanagerConfig
err = fclient.Get(tt.args.ctx, types.NamespacedName{Namespace: tt.args.cr.Namespace, Name: "test-amc"}, &amc)
if err != nil {
t.Fatalf("could not get alert manager config. Error: %v", err)
}

if len(secretConfig.Receivers) != len(amc.Spec.Receivers) {
t.Fatalf("receivers count is wrong. Expected: %v, actual: %v", len(amc.Spec.Receivers), len(secretConfig.Receivers))
}

if len(secretConfig.InhibitRules) != len(amc.Spec.InhibitRules) {
t.Fatalf("inhibit rules count is wrong. Expected: %v, actual: %v", len(amc.Spec.InhibitRules), len(secretConfig.InhibitRules))
}

if !strings.EqualFold(buildCRPrefixedName(&amc, amc.Spec.Route.Receiver), secretConfig.Route.Receiver) {
t.Fatalf("receiver name is wrong. Expected: %v, actual: %v", buildCRPrefixedName(&amc, amc.Spec.Route.Receiver), secretConfig.Route.Receiver)
}
if len(secretConfig.Route.Routes) != 1 {
t.Fatalf("subroutes count is wrong. Expected: %v, actual: %v", 1, len(secretConfig.Route.Routes))
}
if len(secretConfig.Route.Routes[0]) != len(amc.Spec.Route.Routes)+2 { // 2 default routes added
t.Fatalf("subroutes count is wrong. Expected: %v, actual: %v", len(amc.Spec.Route.Routes), len(secretConfig.Route.Routes))
}

// Update secret with alert manager config
if err = createDefaultAMConfig(tt.args.ctx, tt.args.cr, fclient); (err != nil) != tt.wantErr {
t.Fatalf("createDefaultAMConfig() error = %v, wantErr %v", err, tt.wantErr)
}

err = fclient.Get(tt.args.ctx, types.NamespacedName{Namespace: tt.args.cr.Namespace, Name: secretName}, &createdSecret)
if err != nil {
if errors.IsNotFound(err) && tt.secretMustBeMissing {
return
}
t.Fatalf("secret for alertmanager not exist, err: %v", err)
}

// check secret config after updating
d, ok = createdSecret.Data[alertmanagerSecretConfigKey]
if !ok {
t.Fatalf("config for alertmanager not exist, err: %v", err)
}
err = yaml.Unmarshal(d, &secretConfig)
if err != nil {
t.Fatalf("could not unmarshall secret config data into structure, err: %v", err)
}

if len(secretConfig.Receivers) != len(amc.Spec.Receivers) {
t.Fatalf("receivers count is wrong. Expected: %v, actual: %v", len(amc.Spec.Receivers), len(secretConfig.Receivers))
}

if len(secretConfig.InhibitRules) != len(amc.Spec.InhibitRules) {
t.Fatalf("inhibit rules count is wrong. Expected: %v, actual: %v", len(amc.Spec.InhibitRules), len(secretConfig.InhibitRules))
}

if !strings.EqualFold(buildCRPrefixedName(&amc, amc.Spec.Route.Receiver), secretConfig.Route.Receiver) {
t.Fatalf("receiver name is wrong. Expected: %v, actual: %v", buildCRPrefixedName(&amc, amc.Spec.Route.Receiver), secretConfig.Route.Receiver)
}

if len(secretConfig.Route.Routes) != 1 {
t.Fatalf("subroutes count is wrong. Expected: %v, actual: %v", 1, len(secretConfig.Route.Routes))
}
if len(secretConfig.Route.Routes[0]) != len(amc.Spec.Route.Routes)+2 { // 2 default routes added
t.Fatalf("subroutes count is wrong. Expected: %v, actual: %v", len(amc.Spec.Route.Routes), len(secretConfig.Route.Routes))
}
})
}
}
17 changes: 11 additions & 6 deletions controllers/factory/alertmanager/statefulset.go
Original file line number Diff line number Diff line change
Expand Up @@ -452,13 +452,18 @@ func createDefaultAMConfig(ctx context.Context, cr *victoriametricsv1beta1.VMAle
switch {
// fetch content from user defined secret
case cr.Spec.ConfigSecret != "":
// retrieve content
secretContent, err := getSecretContentForAlertmanager(ctx, rclient, cr.Spec.ConfigSecret, cr.Namespace)
if err != nil {
return fmt.Errorf("cannot fetch secret content for alertmanager config secret, err: %w", err)
if cr.Spec.ConfigSecret == cr.ConfigSecretName() {
l.Info("ignoring content of ConfigSecret, since it has the same name as secreted created by operator for config", "secretName", cr.Spec.ConfigSecret)
} else {
// retrieve content
secretContent, err := getSecretContentForAlertmanager(ctx, rclient, cr.Spec.ConfigSecret, cr.Namespace)
if err != nil {
return fmt.Errorf("cannot fetch secret content for alertmanager config secret, err: %w", err)
}
alertmananagerConfig = secretContent

}
alertmananagerConfig = secretContent
// use in-line config
// use in-line config
case cr.Spec.ConfigRawYaml != "":
alertmananagerConfig = []byte(cr.Spec.ConfigRawYaml)
}
Expand Down
2 changes: 2 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ aliases:

## Next release

- [vmalertmanager](./api.md#vmalertmanager): ignores content of `cr.spec.configSecret` if it's name clashes with secret used by operator for storing alertmanager config. See this [issue](https://github.com/VictoriaMetrics/operator/issues/954) for details.

## [v0.44.0](https://github.com/VictoriaMetrics/operator/releases/tag/v0.44.0) - 9 May 2024

- [vmagent](./api.md#vmagent): adds new fields into `streamAggrConfig`: `dedup_interval`, `ignore_old_samples`, `keep_metric_names`, `no_align_flush_to_interval`. It's only possible to use it with v1.100+ version of `vmagent`. See this [issue](https://github.com/VictoriaMetrics/operator/issues/936) for details.
Expand Down

0 comments on commit c8534a5

Please sign in to comment.