1378 lines
39 KiB
Go
1378 lines
39 KiB
Go
/*
|
|
Copyright 2023 The Flux authors
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package server
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
. "github.com/onsi/gomega"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/client-go/tools/record"
|
|
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
log "sigs.k8s.io/controller-runtime/pkg/log"
|
|
|
|
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
|
|
"github.com/fluxcd/pkg/apis/meta"
|
|
"github.com/fluxcd/pkg/auth"
|
|
|
|
apiv1 "github.com/fluxcd/notification-controller/api/v1"
|
|
apiv1beta3 "github.com/fluxcd/notification-controller/api/v1beta3"
|
|
)
|
|
|
|
func TestFilterAlertsForEvent(t *testing.T) {
|
|
testNamespace := "foo-ns"
|
|
|
|
testProvider := &apiv1beta3.Provider{}
|
|
testProvider.Name = "provider-foo"
|
|
testProvider.Namespace = testNamespace
|
|
testProvider.Spec = apiv1beta3.ProviderSpec{
|
|
Type: "generic",
|
|
Address: "https://example.com",
|
|
}
|
|
|
|
// Event involved object.
|
|
involvedObj := corev1.ObjectReference{
|
|
APIVersion: "kustomize.toolkit.fluxcd.io/v1",
|
|
Kind: "Kustomization",
|
|
Name: "foo",
|
|
Namespace: testNamespace,
|
|
}
|
|
testEvent := &eventv1.Event{
|
|
InvolvedObject: involvedObj,
|
|
Message: "some excluded message",
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
alertSpecs []apiv1beta3.AlertSpec
|
|
resultAlertCount int
|
|
}{
|
|
{
|
|
name: "all match",
|
|
alertSpecs: []apiv1beta3.AlertSpec{
|
|
{
|
|
EventSources: []apiv1.CrossNamespaceObjectReference{
|
|
{
|
|
Kind: "Kustomization",
|
|
Name: "*",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
EventSources: []apiv1.CrossNamespaceObjectReference{
|
|
{
|
|
Kind: "Kustomization",
|
|
Name: "foo",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
resultAlertCount: 2,
|
|
},
|
|
{
|
|
name: "some suspended alerts",
|
|
alertSpecs: []apiv1beta3.AlertSpec{
|
|
{
|
|
EventSources: []apiv1.CrossNamespaceObjectReference{
|
|
{
|
|
Kind: "Kustomization",
|
|
Name: "*",
|
|
},
|
|
},
|
|
Suspend: true,
|
|
},
|
|
{
|
|
EventSources: []apiv1.CrossNamespaceObjectReference{
|
|
{
|
|
Kind: "Kustomization",
|
|
Name: "foo",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
resultAlertCount: 1,
|
|
},
|
|
{
|
|
name: "alerts with inclusion list unmatch",
|
|
alertSpecs: []apiv1beta3.AlertSpec{
|
|
{
|
|
EventSources: []apiv1.CrossNamespaceObjectReference{
|
|
{
|
|
Kind: "Kustomization",
|
|
Name: "*",
|
|
},
|
|
},
|
|
InclusionList: []string{"some unmatch include"},
|
|
},
|
|
},
|
|
resultAlertCount: 0,
|
|
},
|
|
{
|
|
name: "alerts with inclusion list match",
|
|
alertSpecs: []apiv1beta3.AlertSpec{
|
|
{
|
|
EventSources: []apiv1.CrossNamespaceObjectReference{
|
|
{
|
|
Kind: "Kustomization",
|
|
Name: "*",
|
|
},
|
|
},
|
|
InclusionList: []string{"some unmatch include"},
|
|
},
|
|
{
|
|
EventSources: []apiv1.CrossNamespaceObjectReference{
|
|
{
|
|
Kind: "Kustomization",
|
|
Name: "*",
|
|
},
|
|
},
|
|
InclusionList: []string{"some"},
|
|
},
|
|
},
|
|
resultAlertCount: 1,
|
|
},
|
|
{
|
|
name: "alerts with invalid inclusion rule",
|
|
alertSpecs: []apiv1beta3.AlertSpec{
|
|
{
|
|
EventSources: []apiv1.CrossNamespaceObjectReference{
|
|
{
|
|
Kind: "Kustomization",
|
|
Name: "*",
|
|
},
|
|
},
|
|
InclusionList: []string{"["},
|
|
},
|
|
},
|
|
resultAlertCount: 0,
|
|
},
|
|
{
|
|
name: "alerts with exclusion list match",
|
|
alertSpecs: []apiv1beta3.AlertSpec{
|
|
{
|
|
EventSources: []apiv1.CrossNamespaceObjectReference{
|
|
{
|
|
Kind: "Kustomization",
|
|
Name: "*",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
EventSources: []apiv1.CrossNamespaceObjectReference{
|
|
{
|
|
Kind: "Kustomization",
|
|
Name: "foo",
|
|
},
|
|
},
|
|
ExclusionList: []string{"excluded message"},
|
|
},
|
|
},
|
|
resultAlertCount: 1,
|
|
},
|
|
{
|
|
name: "alerts with invalid exclusion rule",
|
|
alertSpecs: []apiv1beta3.AlertSpec{
|
|
{
|
|
EventSources: []apiv1.CrossNamespaceObjectReference{
|
|
{
|
|
Kind: "Kustomization",
|
|
Name: "*",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
EventSources: []apiv1.CrossNamespaceObjectReference{
|
|
{
|
|
Kind: "Kustomization",
|
|
Name: "foo",
|
|
},
|
|
},
|
|
ExclusionList: []string{"["},
|
|
},
|
|
},
|
|
resultAlertCount: 2,
|
|
},
|
|
{
|
|
name: "alerts with inclusion and exclusion list match",
|
|
alertSpecs: []apiv1beta3.AlertSpec{
|
|
{
|
|
EventSources: []apiv1.CrossNamespaceObjectReference{
|
|
{
|
|
Kind: "Kustomization",
|
|
Name: "*",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
EventSources: []apiv1.CrossNamespaceObjectReference{
|
|
{
|
|
Kind: "Kustomization",
|
|
Name: "foo",
|
|
},
|
|
},
|
|
InclusionList: []string{"excluded message"},
|
|
ExclusionList: []string{"excluded message"},
|
|
},
|
|
},
|
|
resultAlertCount: 1,
|
|
},
|
|
{
|
|
name: "event source NS is not overwritten by alert NS",
|
|
alertSpecs: []apiv1beta3.AlertSpec{
|
|
{
|
|
EventSources: []apiv1.CrossNamespaceObjectReference{
|
|
{
|
|
Kind: "Kustomization",
|
|
Name: "*",
|
|
Namespace: "foo-bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
resultAlertCount: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
alerts := []apiv1beta3.Alert{}
|
|
for i, alertSpec := range tt.alertSpecs {
|
|
// Add the default provider ref for this test.
|
|
alertSpec.ProviderRef = meta.LocalObjectReference{Name: testProvider.Name}
|
|
// Create new Alert with the spec.
|
|
alert := apiv1beta3.Alert{}
|
|
alert.Name = "test-alert-" + strconv.Itoa(i)
|
|
alert.Namespace = testNamespace
|
|
alert.Spec = alertSpec
|
|
alerts = append(alerts, alert)
|
|
}
|
|
|
|
// Create fake objects and event server.
|
|
scheme := runtime.NewScheme()
|
|
g.Expect(apiv1beta3.AddToScheme(scheme)).ToNot(HaveOccurred())
|
|
builder := fakeclient.NewClientBuilder().WithScheme(scheme)
|
|
builder.WithObjects(testProvider)
|
|
eventServer := EventServer{
|
|
kubeClient: builder.Build(),
|
|
logger: log.Log,
|
|
EventRecorder: record.NewFakeRecorder(32),
|
|
}
|
|
|
|
result := eventServer.filterAlertsForEvent(context.TODO(), alerts, testEvent)
|
|
g.Expect(len(result)).To(Equal(tt.resultAlertCount))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDispatchNotification(t *testing.T) {
|
|
testNamespace := "foo-ns"
|
|
|
|
// Run test notification receiver server.
|
|
rcvServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(200)
|
|
}))
|
|
defer rcvServer.Close()
|
|
|
|
testProvider := &apiv1beta3.Provider{}
|
|
testProvider.Name = "provider-foo"
|
|
testProvider.Namespace = testNamespace
|
|
testProvider.Spec = apiv1beta3.ProviderSpec{
|
|
Type: "generic",
|
|
Address: rcvServer.URL,
|
|
}
|
|
|
|
testAlert := &apiv1beta3.Alert{}
|
|
testAlert.Name = "alert-foo"
|
|
testAlert.Namespace = testNamespace
|
|
testAlert.Spec = apiv1beta3.AlertSpec{
|
|
ProviderRef: meta.LocalObjectReference{Name: testProvider.Name},
|
|
}
|
|
|
|
// Event involved object.
|
|
involvedObj := corev1.ObjectReference{
|
|
APIVersion: "kustomize.toolkit.fluxcd.io/v1",
|
|
Kind: "Kustomization",
|
|
Name: "foo",
|
|
Namespace: testNamespace,
|
|
}
|
|
testEvent := &eventv1.Event{InvolvedObject: involvedObj}
|
|
|
|
tests := []struct {
|
|
name string
|
|
providerNamespace string
|
|
providerSuspended bool
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "dispatch notification successfully",
|
|
},
|
|
{
|
|
name: "provider in different namespace",
|
|
providerNamespace: "bar-ns",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "provider suspended, skip",
|
|
providerSuspended: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
alert := testAlert.DeepCopy()
|
|
provider := testProvider.DeepCopy()
|
|
|
|
// Override the alert and provider with test parameters.
|
|
if tt.providerNamespace != "" {
|
|
provider.Namespace = tt.providerNamespace
|
|
}
|
|
provider.Spec.Suspend = tt.providerSuspended
|
|
|
|
// Create fake objects and event server.
|
|
scheme := runtime.NewScheme()
|
|
g.Expect(apiv1beta3.AddToScheme(scheme)).ToNot(HaveOccurred())
|
|
g.Expect(corev1.AddToScheme(scheme)).ToNot(HaveOccurred())
|
|
builder := fakeclient.NewClientBuilder().WithScheme(scheme)
|
|
builder.WithObjects(provider)
|
|
eventServer := EventServer{
|
|
kubeClient: builder.Build(),
|
|
logger: log.Log,
|
|
EventRecorder: record.NewFakeRecorder(32),
|
|
}
|
|
|
|
err := eventServer.dispatchNotification(context.TODO(), testEvent, alert)
|
|
g.Expect(err != nil).To(Equal(tt.wantErr))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetNotificationParams(t *testing.T) {
|
|
testNamespace := "foo-ns"
|
|
|
|
// Provider secret.
|
|
testSecret := &corev1.Secret{}
|
|
testSecret.Name = "secret-foo"
|
|
testSecret.Namespace = testNamespace
|
|
|
|
testProvider := &apiv1beta3.Provider{}
|
|
testProvider.Name = "provider-foo"
|
|
testProvider.Namespace = testNamespace
|
|
testProvider.Spec = apiv1beta3.ProviderSpec{
|
|
Type: "generic",
|
|
Address: "https://example.com",
|
|
SecretRef: &meta.LocalObjectReference{Name: testSecret.Name},
|
|
}
|
|
|
|
testAlert := &apiv1beta3.Alert{}
|
|
testAlert.Name = "alert-foo"
|
|
testAlert.Namespace = testNamespace
|
|
testAlert.Spec = apiv1beta3.AlertSpec{
|
|
ProviderRef: meta.LocalObjectReference{Name: testProvider.Name},
|
|
}
|
|
|
|
// Event involved object.
|
|
involvedObj := corev1.ObjectReference{
|
|
APIVersion: "kustomize.toolkit.fluxcd.io/v1",
|
|
Kind: "Kustomization",
|
|
Name: "foo",
|
|
Namespace: testNamespace,
|
|
}
|
|
testEvent := &eventv1.Event{InvolvedObject: involvedObj}
|
|
|
|
tests := []struct {
|
|
name string
|
|
alertNamespace string
|
|
alertSummary string
|
|
alertEventMetadata map[string]string
|
|
providerNamespace string
|
|
providerSuspended bool
|
|
providerServiceAccount string
|
|
secretNamespace string
|
|
noCrossNSRefs bool
|
|
enableObjLevelWI bool
|
|
eventMetadata map[string]string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "event src and alert in diff NS",
|
|
alertNamespace: "bar-ns",
|
|
providerNamespace: "bar-ns",
|
|
secretNamespace: "bar-ns",
|
|
},
|
|
{
|
|
name: "event src and alert in diff NS with no cross NS refs",
|
|
alertNamespace: "bar-ns",
|
|
providerNamespace: "bar-ns",
|
|
noCrossNSRefs: true,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "provider not found",
|
|
providerNamespace: "bar-ns",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "provider secret in diff NS but provider suspended",
|
|
providerSuspended: true,
|
|
secretNamespace: "bar-ns",
|
|
},
|
|
{
|
|
name: "provider secret in different NS, fail to create notifier",
|
|
secretNamespace: "bar-ns",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "alert with summary, no event metadata",
|
|
alertSummary: "some summary text",
|
|
},
|
|
{
|
|
name: "alert with summary, with event metadata",
|
|
alertSummary: "some summary text",
|
|
eventMetadata: map[string]string{
|
|
"foo": "bar",
|
|
"summary": "baz",
|
|
},
|
|
},
|
|
{
|
|
name: "alert with event metadata",
|
|
alertEventMetadata: map[string]string{
|
|
"aaa": "bbb",
|
|
"ccc": "ddd",
|
|
},
|
|
},
|
|
{
|
|
name: "object level workload identity feature gate disabled",
|
|
providerServiceAccount: "foo",
|
|
enableObjLevelWI: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "object level workload identity feature gate enabled",
|
|
providerServiceAccount: "foo",
|
|
enableObjLevelWI: true,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
alert := testAlert.DeepCopy()
|
|
provider := testProvider.DeepCopy()
|
|
secret := testSecret.DeepCopy()
|
|
event := testEvent.DeepCopy()
|
|
|
|
// Override the alert, provider, secret and event with test
|
|
// parameters.
|
|
if tt.alertNamespace != "" {
|
|
alert.Namespace = tt.alertNamespace
|
|
}
|
|
if tt.alertSummary != "" {
|
|
alert.Spec.Summary = tt.alertSummary
|
|
}
|
|
if tt.alertEventMetadata != nil {
|
|
alert.Spec.EventMetadata = tt.alertEventMetadata
|
|
}
|
|
if tt.providerNamespace != "" {
|
|
provider.Namespace = tt.providerNamespace
|
|
}
|
|
provider.Spec.Suspend = tt.providerSuspended
|
|
provider.Spec.ServiceAccountName = tt.providerServiceAccount
|
|
if tt.secretNamespace != "" {
|
|
secret.Namespace = tt.secretNamespace
|
|
}
|
|
if tt.eventMetadata != nil {
|
|
event.Metadata = tt.eventMetadata
|
|
}
|
|
|
|
if tt.enableObjLevelWI {
|
|
t.Setenv(auth.EnvVarEnableObjectLevelWorkloadIdentity, "true")
|
|
}
|
|
|
|
// Create fake objects and event server.
|
|
scheme := runtime.NewScheme()
|
|
g.Expect(apiv1beta3.AddToScheme(scheme)).ToNot(HaveOccurred())
|
|
g.Expect(corev1.AddToScheme(scheme)).ToNot(HaveOccurred())
|
|
builder := fakeclient.NewClientBuilder().WithScheme(scheme)
|
|
builder.WithObjects(provider, secret)
|
|
eventServer := EventServer{
|
|
kubeClient: builder.Build(),
|
|
logger: log.Log,
|
|
noCrossNamespaceRefs: tt.noCrossNSRefs,
|
|
EventRecorder: record.NewFakeRecorder(32),
|
|
}
|
|
|
|
_, n, _, _, err := eventServer.getNotificationParams(context.TODO(), event, alert)
|
|
g.Expect(err != nil).To(Equal(tt.wantErr))
|
|
if tt.alertSummary != "" {
|
|
g.Expect(n.Metadata["summary"]).To(Equal(tt.alertSummary))
|
|
}
|
|
// NOTE: This is performing simple check. Thorough test for event
|
|
// metadata is performed in TestCombineEventMetadata.
|
|
if tt.alertEventMetadata != nil {
|
|
for k, v := range tt.alertEventMetadata {
|
|
g.Expect(n.Metadata).To(HaveKeyWithValue(k, v))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCreateNotifier(t *testing.T) {
|
|
secretName := "foo-secret"
|
|
certSecretName := "cert-secret"
|
|
tests := []struct {
|
|
name string
|
|
providerSpec *apiv1beta3.ProviderSpec
|
|
secretType corev1.SecretType
|
|
secretData map[string][]byte
|
|
certSecretData map[string][]byte
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "no address, no secret ref",
|
|
providerSpec: &apiv1beta3.ProviderSpec{
|
|
Type: "slack",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "valid address, no secret ref",
|
|
providerSpec: &apiv1beta3.ProviderSpec{
|
|
Type: "slack",
|
|
Address: "https://example.com",
|
|
},
|
|
},
|
|
{
|
|
name: "reference to non-existing secret ref",
|
|
providerSpec: &apiv1beta3.ProviderSpec{
|
|
Type: "slack",
|
|
SecretRef: &meta.LocalObjectReference{Name: "foo"},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "reference to secret with valid address, proxy, headers",
|
|
providerSpec: &apiv1beta3.ProviderSpec{
|
|
Type: "slack",
|
|
SecretRef: &meta.LocalObjectReference{Name: secretName},
|
|
},
|
|
secretData: map[string][]byte{
|
|
"address": []byte("https://example.com"),
|
|
"proxy": []byte("https://exampleproxy.com"),
|
|
"headers": []byte(`foo: bar`),
|
|
},
|
|
},
|
|
{
|
|
name: "reference to secret with invalid proxy",
|
|
providerSpec: &apiv1beta3.ProviderSpec{
|
|
Type: "slack",
|
|
SecretRef: &meta.LocalObjectReference{Name: secretName},
|
|
},
|
|
secretData: map[string][]byte{
|
|
"address": []byte("https://example.com"),
|
|
"proxy": []byte("https://exampleproxy.com|"),
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid headers in secret reference",
|
|
providerSpec: &apiv1beta3.ProviderSpec{
|
|
Type: "slack",
|
|
SecretRef: &meta.LocalObjectReference{Name: secretName},
|
|
},
|
|
secretData: map[string][]byte{
|
|
"address": []byte("https://example.com"),
|
|
"headers": []byte("foo"),
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid spec address overridden by valid secret ref address",
|
|
providerSpec: &apiv1beta3.ProviderSpec{
|
|
Type: "slack",
|
|
SecretRef: &meta.LocalObjectReference{Name: secretName},
|
|
Address: "https://example.com|",
|
|
},
|
|
secretData: map[string][]byte{
|
|
"address": []byte("https://example.com"),
|
|
},
|
|
},
|
|
{
|
|
name: "invalid spec proxy overridden by valid secret ref proxy",
|
|
providerSpec: &apiv1beta3.ProviderSpec{
|
|
Type: "slack",
|
|
SecretRef: &meta.LocalObjectReference{Name: secretName},
|
|
Proxy: "https://example.com|",
|
|
},
|
|
secretData: map[string][]byte{
|
|
"address": []byte("https://example.com"),
|
|
"proxy": []byte("https://example.com"),
|
|
},
|
|
},
|
|
{
|
|
name: "reference to unsupported cert secret type",
|
|
providerSpec: &apiv1beta3.ProviderSpec{
|
|
Type: "slack",
|
|
Address: "https://example.com",
|
|
CertSecretRef: &meta.LocalObjectReference{Name: certSecretName},
|
|
},
|
|
secretType: corev1.SecretTypeDockercfg,
|
|
certSecretData: map[string][]byte{
|
|
"aaa": []byte("bbb"),
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "reference to non-existing cert secret",
|
|
providerSpec: &apiv1beta3.ProviderSpec{
|
|
Type: "slack",
|
|
Address: "https://example.com",
|
|
CertSecretRef: &meta.LocalObjectReference{Name: "some-secret"},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "reference to cert secret without cert data",
|
|
providerSpec: &apiv1beta3.ProviderSpec{
|
|
Type: "slack",
|
|
Address: "https://example.com",
|
|
CertSecretRef: &meta.LocalObjectReference{Name: certSecretName},
|
|
},
|
|
certSecretData: map[string][]byte{
|
|
"aaa": []byte("bbb"),
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "cert secret reference in ca.crt with valid CA",
|
|
providerSpec: &apiv1beta3.ProviderSpec{
|
|
Type: "slack",
|
|
Address: "https://example.com",
|
|
CertSecretRef: &meta.LocalObjectReference{Name: certSecretName},
|
|
},
|
|
certSecretData: map[string][]byte{
|
|
// Based on https://pkg.go.dev/crypto/tls#X509KeyPair example.
|
|
"ca.crt": []byte(`-----BEGIN CERTIFICATE-----
|
|
MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw
|
|
DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow
|
|
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d
|
|
7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B
|
|
5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr
|
|
BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1
|
|
NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l
|
|
Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
|
|
6MF9+Yw1Yy0t
|
|
-----END CERTIFICATE-----`),
|
|
},
|
|
},
|
|
{
|
|
name: "cert secret reference in caFile with valid CA",
|
|
providerSpec: &apiv1beta3.ProviderSpec{
|
|
Type: "slack",
|
|
Address: "https://example.com",
|
|
CertSecretRef: &meta.LocalObjectReference{Name: certSecretName},
|
|
},
|
|
certSecretData: map[string][]byte{
|
|
// Based on https://pkg.go.dev/crypto/tls#X509KeyPair example.
|
|
"caFile": []byte(`-----BEGIN CERTIFICATE-----
|
|
MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw
|
|
DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow
|
|
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d
|
|
7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B
|
|
5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr
|
|
BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1
|
|
NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l
|
|
Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
|
|
6MF9+Yw1Yy0t
|
|
-----END CERTIFICATE-----`),
|
|
},
|
|
},
|
|
{
|
|
name: "cert secret reference in both ca.crt and caFile",
|
|
providerSpec: &apiv1beta3.ProviderSpec{
|
|
Type: "slack",
|
|
Address: "https://example.com",
|
|
CertSecretRef: &meta.LocalObjectReference{Name: certSecretName},
|
|
},
|
|
certSecretData: map[string][]byte{
|
|
// Based on https://pkg.go.dev/crypto/tls#X509KeyPair example.
|
|
"ca.crt": []byte(`-----BEGIN CERTIFICATE-----
|
|
MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw
|
|
DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow
|
|
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d
|
|
7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B
|
|
5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr
|
|
BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1
|
|
NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l
|
|
Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
|
|
6MF9+Yw1Yy0t
|
|
-----END CERTIFICATE-----`),
|
|
"caFile": []byte(`aaaaa`), // invalid
|
|
},
|
|
},
|
|
{
|
|
name: "cert secret reference with invalid CA",
|
|
providerSpec: &apiv1beta3.ProviderSpec{
|
|
Type: "slack",
|
|
Address: "https://example.com",
|
|
CertSecretRef: &meta.LocalObjectReference{Name: certSecretName},
|
|
},
|
|
certSecretData: map[string][]byte{
|
|
"ca.crt": []byte(`aaaaa`),
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "unsupported provider",
|
|
providerSpec: &apiv1beta3.ProviderSpec{
|
|
Type: "foo",
|
|
Address: "https://example.com",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "address in secret too long",
|
|
providerSpec: &apiv1beta3.ProviderSpec{
|
|
Type: "msteams",
|
|
SecretRef: &meta.LocalObjectReference{Name: secretName},
|
|
},
|
|
secretData: map[string][]byte{
|
|
"address": []byte(fmt.Sprintf("https://example.org/%s", strings.Repeat("a", 2029))),
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "address in secret exactly as long as possible",
|
|
providerSpec: &apiv1beta3.ProviderSpec{
|
|
Type: "msteams",
|
|
SecretRef: &meta.LocalObjectReference{Name: secretName},
|
|
},
|
|
secretData: map[string][]byte{
|
|
"address": []byte(fmt.Sprintf("https://example.org/%s", strings.Repeat("a", 2028))),
|
|
},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
// Create fake objects and event server.
|
|
scheme := runtime.NewScheme()
|
|
g.Expect(apiv1beta3.AddToScheme(scheme)).ToNot(HaveOccurred())
|
|
g.Expect(corev1.AddToScheme(scheme)).ToNot(HaveOccurred())
|
|
builder := fakeclient.NewClientBuilder().WithScheme(scheme)
|
|
if tt.secretData != nil {
|
|
secret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{Name: secretName},
|
|
Data: tt.secretData,
|
|
}
|
|
builder.WithObjects(secret)
|
|
}
|
|
if tt.certSecretData != nil {
|
|
secret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{Name: certSecretName},
|
|
Type: tt.secretType,
|
|
Data: tt.certSecretData,
|
|
}
|
|
builder.WithObjects(secret)
|
|
}
|
|
provider := apiv1beta3.Provider{Spec: *tt.providerSpec}
|
|
|
|
_, _, err := createNotifier(context.TODO(), builder.Build(), &provider, "", nil)
|
|
g.Expect(err != nil).To(Equal(tt.wantErr))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCreateCommitStatus(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
provider apiv1beta3.Provider
|
|
notification eventv1.Event
|
|
alert *apiv1beta3.Alert
|
|
wantCommitStatus string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "non-git provider: slack",
|
|
provider: apiv1beta3.Provider{
|
|
Spec: apiv1beta3.ProviderSpec{
|
|
Type: "slack",
|
|
},
|
|
},
|
|
wantCommitStatus: "",
|
|
},
|
|
{
|
|
name: "non-git provider: msteams",
|
|
provider: apiv1beta3.Provider{
|
|
Spec: apiv1beta3.ProviderSpec{
|
|
Type: "msteams",
|
|
},
|
|
},
|
|
wantCommitStatus: "",
|
|
},
|
|
{
|
|
name: "git provider without commit status expression",
|
|
provider: apiv1beta3.Provider{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
UID: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
|
},
|
|
Spec: apiv1beta3.ProviderSpec{
|
|
Type: "github",
|
|
},
|
|
},
|
|
notification: eventv1.Event{
|
|
InvolvedObject: corev1.ObjectReference{
|
|
Kind: "Kustomization",
|
|
Name: "gitops-system",
|
|
},
|
|
Reason: "ApplySucceeded",
|
|
},
|
|
wantCommitStatus: "kustomization/gitops-system/0c9c2e41",
|
|
},
|
|
{
|
|
name: "gitlab provider without commit status expression",
|
|
provider: apiv1beta3.Provider{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
UID: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
|
},
|
|
Spec: apiv1beta3.ProviderSpec{
|
|
Type: "gitlab",
|
|
},
|
|
},
|
|
notification: eventv1.Event{
|
|
InvolvedObject: corev1.ObjectReference{
|
|
Kind: "HelmRelease",
|
|
Name: "gitops-system",
|
|
},
|
|
Reason: "ApplySucceeded",
|
|
},
|
|
wantCommitStatus: "helmrelease/gitops-system/0c9c2e41",
|
|
},
|
|
{
|
|
name: "git provider with commit status expression",
|
|
provider: apiv1beta3.Provider{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
UID: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
|
},
|
|
Spec: apiv1beta3.ProviderSpec{
|
|
Type: "github",
|
|
CommitStatusExpr: "event.involvedObject.kind + '/' + event.involvedObject.name + '/' + event.metadata.environment + '/' + provider.metadata.uid",
|
|
},
|
|
},
|
|
notification: eventv1.Event{
|
|
InvolvedObject: corev1.ObjectReference{
|
|
Kind: "Kustomization",
|
|
Name: "gitops-system",
|
|
},
|
|
Reason: "ApplySucceeded",
|
|
Metadata: map[string]string{
|
|
"environment": "production",
|
|
},
|
|
},
|
|
alert: &apiv1beta3.Alert{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-alert",
|
|
},
|
|
},
|
|
wantCommitStatus: "Kustomization/gitops-system/production/0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
|
},
|
|
{
|
|
name: "git provider with commit status expression using first value of provider UID",
|
|
provider: apiv1beta3.Provider{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
UID: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
|
},
|
|
Spec: apiv1beta3.ProviderSpec{
|
|
Type: "github",
|
|
CommitStatusExpr: "event.involvedObject.kind + '/' + event.involvedObject.name + '/' + event.metadata.environment + '/' + provider.metadata.uid.split('-').first().value()",
|
|
},
|
|
},
|
|
notification: eventv1.Event{
|
|
InvolvedObject: corev1.ObjectReference{
|
|
Kind: "Kustomization",
|
|
Name: "gitops-system",
|
|
},
|
|
Metadata: map[string]string{
|
|
"environment": "production",
|
|
},
|
|
},
|
|
alert: &apiv1beta3.Alert{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-alert",
|
|
},
|
|
},
|
|
wantCommitStatus: "Kustomization/gitops-system/production/0c9c2e41",
|
|
},
|
|
{
|
|
name: "git provider with commit status expression using event, alert, and provider",
|
|
provider: apiv1beta3.Provider{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
UID: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a",
|
|
},
|
|
Spec: apiv1beta3.ProviderSpec{
|
|
Type: "github",
|
|
CommitStatusExpr: "event.involvedObject.kind + '/' + event.involvedObject.name + '/' + event.metadata.environment + '/' + provider.metadata.uid + '/' + alert.metadata.name",
|
|
},
|
|
},
|
|
notification: eventv1.Event{
|
|
InvolvedObject: corev1.ObjectReference{
|
|
Kind: "Kustomization",
|
|
Name: "gitops-system",
|
|
},
|
|
Metadata: map[string]string{
|
|
"environment": "production",
|
|
},
|
|
},
|
|
alert: &apiv1beta3.Alert{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-alert",
|
|
},
|
|
},
|
|
wantCommitStatus: "Kustomization/gitops-system/production/0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a/test-alert",
|
|
},
|
|
{
|
|
name: "git provider with invalid commit status expression referencing non-existent event metadata",
|
|
notification: eventv1.Event{
|
|
InvolvedObject: corev1.ObjectReference{
|
|
Kind: "Kustomization",
|
|
Name: "gitops-system",
|
|
},
|
|
Metadata: map[string]string{
|
|
"foo": "bar",
|
|
},
|
|
},
|
|
provider: apiv1beta3.Provider{
|
|
Spec: apiv1beta3.ProviderSpec{
|
|
Type: "github",
|
|
CommitStatusExpr: "event.involvedObject.kind + '/' + event.involvedObject.name + '/' + event.metadata.notfound + '/' + provider.metadata.uid",
|
|
},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "git provider with invalid commit status expression",
|
|
notification: eventv1.Event{
|
|
InvolvedObject: corev1.ObjectReference{
|
|
Kind: "Kustomization",
|
|
Name: "gitops-system",
|
|
},
|
|
Metadata: map[string]string{
|
|
"foo": "bar",
|
|
},
|
|
},
|
|
provider: apiv1beta3.Provider{
|
|
Spec: apiv1beta3.ProviderSpec{
|
|
Type: "github",
|
|
CommitStatusExpr: "event.involvedObject.kind == 'Kustomization'",
|
|
},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
// Create fake objects and event server.
|
|
scheme := runtime.NewScheme()
|
|
g.Expect(apiv1beta3.AddToScheme(scheme)).ToNot(HaveOccurred())
|
|
g.Expect(corev1.AddToScheme(scheme)).ToNot(HaveOccurred())
|
|
|
|
commitStatus, err := createCommitStatus(context.TODO(), &tt.provider, &tt.notification, tt.alert)
|
|
g.Expect(err != nil).To(Equal(tt.wantErr))
|
|
g.Expect(commitStatus).To(Equal(tt.wantCommitStatus))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEventMatchesAlert(t *testing.T) {
|
|
testNamespace := "foo-ns"
|
|
involvedObj := corev1.ObjectReference{
|
|
APIVersion: "kustomize.toolkit.fluxcd.io/v1",
|
|
Kind: "Kustomization",
|
|
Name: "foo",
|
|
Namespace: testNamespace,
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
event *eventv1.Event
|
|
source apiv1.CrossNamespaceObjectReference
|
|
severity string
|
|
resourcesFile string
|
|
wantResult bool
|
|
}{
|
|
{
|
|
name: "source and event namespace mismatch",
|
|
event: &eventv1.Event{InvolvedObject: involvedObj},
|
|
source: apiv1.CrossNamespaceObjectReference{
|
|
Kind: "Kustomization",
|
|
Name: "*",
|
|
Namespace: "test-ns",
|
|
},
|
|
severity: "info",
|
|
wantResult: false,
|
|
},
|
|
{
|
|
name: "source and event kind mismatch",
|
|
event: &eventv1.Event{InvolvedObject: involvedObj},
|
|
source: apiv1.CrossNamespaceObjectReference{
|
|
Kind: "GitRepository",
|
|
Name: "*",
|
|
Namespace: testNamespace,
|
|
},
|
|
severity: "info",
|
|
wantResult: false,
|
|
},
|
|
{
|
|
name: "event and alert severity mismatch, alert severity error",
|
|
event: &eventv1.Event{
|
|
InvolvedObject: involvedObj,
|
|
Severity: "info",
|
|
},
|
|
source: apiv1.CrossNamespaceObjectReference{
|
|
Kind: "Kustomization",
|
|
Name: "*",
|
|
Namespace: testNamespace,
|
|
},
|
|
severity: "error",
|
|
wantResult: false,
|
|
},
|
|
{
|
|
name: "event and alert severity mismatch, alert severity info",
|
|
event: &eventv1.Event{
|
|
InvolvedObject: involvedObj,
|
|
Severity: "error",
|
|
},
|
|
source: apiv1.CrossNamespaceObjectReference{
|
|
Kind: "Kustomization",
|
|
Name: "*",
|
|
Namespace: testNamespace,
|
|
},
|
|
severity: "info",
|
|
wantResult: true,
|
|
},
|
|
{
|
|
name: "source with matching kind and namespace, any name",
|
|
event: &eventv1.Event{InvolvedObject: involvedObj},
|
|
source: apiv1.CrossNamespaceObjectReference{
|
|
Kind: "Kustomization",
|
|
Name: "*",
|
|
Namespace: testNamespace,
|
|
},
|
|
severity: "info",
|
|
wantResult: true,
|
|
},
|
|
{
|
|
name: "source with matching kind and namespace, unmatched name",
|
|
event: &eventv1.Event{InvolvedObject: involvedObj},
|
|
source: apiv1.CrossNamespaceObjectReference{
|
|
Kind: "Kustomization",
|
|
Name: "bar",
|
|
Namespace: testNamespace,
|
|
},
|
|
severity: "info",
|
|
wantResult: false,
|
|
},
|
|
{
|
|
name: "source with matching kind and namespace, matched name",
|
|
event: &eventv1.Event{InvolvedObject: involvedObj},
|
|
source: apiv1.CrossNamespaceObjectReference{
|
|
Kind: "Kustomization",
|
|
Name: "foo",
|
|
Namespace: testNamespace,
|
|
},
|
|
severity: "info",
|
|
wantResult: true,
|
|
},
|
|
{
|
|
name: "label selector match",
|
|
resourcesFile: "./testdata/kustomization.yaml",
|
|
event: &eventv1.Event{InvolvedObject: involvedObj},
|
|
source: apiv1.CrossNamespaceObjectReference{
|
|
Kind: "Kustomization",
|
|
Name: "*",
|
|
Namespace: testNamespace,
|
|
MatchLabels: map[string]string{
|
|
"app": "podinfo",
|
|
},
|
|
},
|
|
severity: "info",
|
|
wantResult: true,
|
|
},
|
|
{
|
|
name: "label selector mismatch",
|
|
resourcesFile: "./testdata/kustomization.yaml",
|
|
event: &eventv1.Event{InvolvedObject: involvedObj},
|
|
source: apiv1.CrossNamespaceObjectReference{
|
|
Kind: "Kustomization",
|
|
Name: "*",
|
|
Namespace: testNamespace,
|
|
MatchLabels: map[string]string{
|
|
"aaa": "bbb",
|
|
},
|
|
},
|
|
severity: "info",
|
|
wantResult: false,
|
|
},
|
|
{
|
|
name: "label selector, object not found",
|
|
event: &eventv1.Event{InvolvedObject: involvedObj},
|
|
source: apiv1.CrossNamespaceObjectReference{
|
|
Kind: "Kustomization",
|
|
Name: "*",
|
|
Namespace: testNamespace,
|
|
MatchLabels: map[string]string{
|
|
"aaa": "bbb",
|
|
},
|
|
},
|
|
severity: "info",
|
|
wantResult: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
scheme := runtime.NewScheme()
|
|
g.Expect(apiv1beta3.AddToScheme(scheme)).ToNot(HaveOccurred())
|
|
|
|
builder := fakeclient.NewClientBuilder().WithScheme(scheme)
|
|
|
|
// Create pre-existing resource from manifest file.
|
|
if tt.resourcesFile != "" {
|
|
obj, err := readManifest(tt.resourcesFile, testNamespace)
|
|
g.Expect(err).ToNot(HaveOccurred())
|
|
builder.WithObjects(obj)
|
|
}
|
|
|
|
eventServer := EventServer{
|
|
kubeClient: builder.Build(),
|
|
logger: log.Log,
|
|
EventRecorder: record.NewFakeRecorder(32),
|
|
}
|
|
alert := &apiv1beta3.Alert{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-alert",
|
|
Namespace: "test-ns",
|
|
},
|
|
Spec: apiv1beta3.AlertSpec{
|
|
EventSeverity: tt.severity,
|
|
},
|
|
}
|
|
|
|
result := eventServer.eventMatchesAlertSource(context.TODO(), tt.event, alert, tt.source)
|
|
g.Expect(result).To(Equal(tt.wantResult))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCombineEventMetadata(t *testing.T) {
|
|
for name, tt := range map[string]struct {
|
|
event eventv1.Event
|
|
alert apiv1beta3.Alert
|
|
expectedMetadata map[string]string
|
|
conflictEvent string
|
|
}{
|
|
"empty metadata": {
|
|
event: eventv1.Event{},
|
|
alert: apiv1beta3.Alert{},
|
|
expectedMetadata: nil,
|
|
},
|
|
"all metadata sources work": {
|
|
event: eventv1.Event{
|
|
Metadata: map[string]string{
|
|
"kustomize.toolkit.fluxcd.io/controllerMetadata1": "controllerMetadataValue1",
|
|
"kustomize.toolkit.fluxcd.io/controllerMetadata2": "controllerMetadataValue2",
|
|
"event.toolkit.fluxcd.io/objectMetadata1": "objectMetadataValue1",
|
|
"event.toolkit.fluxcd.io/objectMetadata2": "objectMetadataValue2",
|
|
},
|
|
},
|
|
alert: apiv1beta3.Alert{
|
|
Spec: apiv1beta3.AlertSpec{
|
|
Summary: "summaryValue",
|
|
EventMetadata: map[string]string{
|
|
"foo": "bar",
|
|
"baz": "qux",
|
|
},
|
|
},
|
|
},
|
|
expectedMetadata: map[string]string{
|
|
"foo": "bar",
|
|
"baz": "qux",
|
|
"controllerMetadata1": "controllerMetadataValue1",
|
|
"controllerMetadata2": "controllerMetadataValue2",
|
|
"summary": "summaryValue",
|
|
"objectMetadata1": "objectMetadataValue1",
|
|
"objectMetadata2": "objectMetadataValue2",
|
|
},
|
|
},
|
|
"object metadata is overriden by summary": {
|
|
event: eventv1.Event{
|
|
Metadata: map[string]string{
|
|
"event.toolkit.fluxcd.io/summary": "objectSummary",
|
|
},
|
|
},
|
|
alert: apiv1beta3.Alert{
|
|
Spec: apiv1beta3.AlertSpec{
|
|
Summary: "alertSummary",
|
|
},
|
|
},
|
|
expectedMetadata: map[string]string{
|
|
"summary": "alertSummary",
|
|
},
|
|
conflictEvent: "Warning MetadataAppendFailed metadata key conflicts detected (please refer to the Alert API docs and Flux RFC 0008 for more information) map[summary:involved object annotations, Alert object .spec.summary]",
|
|
},
|
|
"alert event metadata is overriden by summary": {
|
|
event: eventv1.Event{},
|
|
alert: apiv1beta3.Alert{
|
|
Spec: apiv1beta3.AlertSpec{
|
|
Summary: "alertSummary",
|
|
EventMetadata: map[string]string{
|
|
"summary": "eventMetadataSummary",
|
|
},
|
|
},
|
|
},
|
|
expectedMetadata: map[string]string{
|
|
"summary": "alertSummary",
|
|
},
|
|
conflictEvent: "Warning MetadataAppendFailed metadata key conflicts detected (please refer to the Alert API docs and Flux RFC 0008 for more information) map[summary:Alert object .spec.eventMetadata, Alert object .spec.summary]",
|
|
},
|
|
"summary is overriden by controller metadata": {
|
|
event: eventv1.Event{
|
|
Metadata: map[string]string{
|
|
"kustomize.toolkit.fluxcd.io/summary": "controllerSummary",
|
|
},
|
|
},
|
|
alert: apiv1beta3.Alert{
|
|
Spec: apiv1beta3.AlertSpec{
|
|
Summary: "alertSummary",
|
|
},
|
|
},
|
|
expectedMetadata: map[string]string{
|
|
"summary": "controllerSummary",
|
|
},
|
|
conflictEvent: "Warning MetadataAppendFailed metadata key conflicts detected (please refer to the Alert API docs and Flux RFC 0008 for more information) map[summary:Alert object .spec.summary, involved object controller metadata]",
|
|
},
|
|
"precedence order in RFC 0008 is honered": {
|
|
event: eventv1.Event{
|
|
Metadata: map[string]string{
|
|
"kustomize.toolkit.fluxcd.io/objectMetadataOverridenByController": "controllerMetadataValue1",
|
|
"kustomize.toolkit.fluxcd.io/alertMetadataOverridenByController": "controllerMetadataValue2",
|
|
"kustomize.toolkit.fluxcd.io/controllerMetadata": "controllerMetadataValue3",
|
|
"event.toolkit.fluxcd.io/objectMetadata": "objectMetadataValue1",
|
|
"event.toolkit.fluxcd.io/objectMetadataOverridenByAlert": "objectMetadataValue2",
|
|
"event.toolkit.fluxcd.io/objectMetadataOverridenByController": "objectMetadataValue3",
|
|
},
|
|
},
|
|
alert: apiv1beta3.Alert{
|
|
Spec: apiv1beta3.AlertSpec{
|
|
EventMetadata: map[string]string{
|
|
"objectMetadataOverridenByAlert": "alertMetadataValue1",
|
|
"alertMetadata": "alertMetadataValue2",
|
|
"alertMetadataOverridenByController": "alertMetadataValue3",
|
|
},
|
|
},
|
|
},
|
|
expectedMetadata: map[string]string{
|
|
"objectMetadata": "objectMetadataValue1",
|
|
"objectMetadataOverridenByAlert": "alertMetadataValue1",
|
|
"objectMetadataOverridenByController": "controllerMetadataValue1",
|
|
"alertMetadata": "alertMetadataValue2",
|
|
"alertMetadataOverridenByController": "controllerMetadataValue2",
|
|
"controllerMetadata": "controllerMetadataValue3",
|
|
},
|
|
conflictEvent: "Warning MetadataAppendFailed metadata key conflicts detected (please refer to the Alert API docs and Flux RFC 0008 for more information) map[alertMetadataOverridenByController:Alert object .spec.eventMetadata, involved object controller metadata objectMetadataOverridenByAlert:involved object annotations, Alert object .spec.eventMetadata objectMetadataOverridenByController:involved object annotations, involved object controller metadata]",
|
|
},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
g := NewGomegaWithT(t)
|
|
|
|
eventRecorder := record.NewFakeRecorder(1)
|
|
s := &EventServer{
|
|
logger: log.Log,
|
|
EventRecorder: eventRecorder,
|
|
}
|
|
|
|
tt.event.InvolvedObject.APIVersion = "kustomize.toolkit.fluxcd.io/v1"
|
|
s.combineEventMetadata(context.Background(), &tt.event, &tt.alert)
|
|
g.Expect(tt.event.Metadata).To(BeEquivalentTo(tt.expectedMetadata))
|
|
|
|
var event string
|
|
select {
|
|
case event = <-eventRecorder.Events:
|
|
default:
|
|
}
|
|
g.Expect(event).To(Equal(tt.conflictEvent))
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_excludeInternalMetadata(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
event eventv1.Event
|
|
wantMetadata map[string]string
|
|
}{
|
|
{
|
|
name: "no metadata",
|
|
},
|
|
{
|
|
name: "internal metadata",
|
|
event: eventv1.Event{
|
|
InvolvedObject: corev1.ObjectReference{
|
|
APIVersion: "kustomize.toolkit.fluxcd.io/v1",
|
|
},
|
|
Metadata: map[string]string{
|
|
"kustomize.toolkit.fluxcd.io/" + eventv1.MetaTokenKey: "aaaa",
|
|
"kustomize.toolkit.fluxcd.io/" + eventv1.MetaRevisionKey: "bbbb",
|
|
},
|
|
},
|
|
wantMetadata: map[string]string{
|
|
"kustomize.toolkit.fluxcd.io/" + eventv1.MetaRevisionKey: "bbbb",
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
g := NewWithT(t)
|
|
|
|
excludeInternalMetadata(&tt.event)
|
|
g.Expect(tt.event.Metadata).To(BeEquivalentTo(tt.wantMetadata))
|
|
})
|
|
}
|
|
}
|