diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 07900a86..f0752d85 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1alpha1 import ( - v1 "k8s.io/api/core/v1" + "k8s.io/api/core/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 9ba70469..7c626537 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1beta1 import ( - v1 "k8s.io/api/core/v1" + "k8s.io/api/core/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/go.mod b/go.mod index 2b281ecf..2d5e4c74 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/Masterminds/sprig/v3 v3.3.0 github.com/go-logr/logr v1.4.2 github.com/google/gofuzz v1.2.0 + github.com/jbogarin/go-cisco-webex-teams v0.4.3 github.com/mocktools/go-smtp-mock/v2 v2.4.0 github.com/onsi/ginkgo/v2 v2.22.0 github.com/onsi/gomega v1.36.0 @@ -36,12 +37,12 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-errors/errors v1.5.1 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-resty/resty/v2 v2.7.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gobuffalo/flect v1.0.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -49,6 +50,7 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect @@ -64,6 +66,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/peterhellberg/link v1.1.0 // indirect github.com/prometheus/client_golang v1.20.4 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.60.0 // indirect diff --git a/go.sum b/go.sum index 04c33755..b1c92a7e 100644 --- a/go.sum +++ b/go.sum @@ -26,8 +26,6 @@ github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0 github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= @@ -42,6 +40,8 @@ github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= +github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= @@ -54,9 +54,12 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -70,6 +73,8 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/jbogarin/go-cisco-webex-teams v0.4.3 h1:gXRwLi5qNmqNS3EXj9ikVhXWNXGzbc4RFFzOtTA1WYE= +github.com/jbogarin/go-cisco-webex-teams v0.4.3/go.mod h1:UVLsP1NjEO9QfRcZsuO6R4AD/xMJLarkLuhcehSZlAA= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -105,6 +110,8 @@ github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.36.0 h1:Pb12RlruUtj4XUuPUqeEWc6j5DkVVVA49Uf6YLfC95Y= github.com/onsi/gomega v1.36.0/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/peterhellberg/link v1.1.0 h1:s2+RH8EGuI/mI4QwrWGSYQCRz7uNgip9BaM04HKu5kc= +github.com/peterhellberg/link v1.1.0/go.mod h1:gtSlOT4jmkY8P47hbTc8PTgiDDWpdPbFYl75keYyBB8= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -166,6 +173,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= @@ -176,12 +185,16 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= @@ -235,8 +248,6 @@ k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 h1:jGnCPejIetjiy2gqaJ5V0NLwTpF4w k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/cluster-api v1.8.5 h1:lNA2fPN4fkXEs+oOQlnwxT/4VwRFBpv5kkSoJG8nqBA= sigs.k8s.io/cluster-api v1.8.5/go.mod h1:pXv5LqLxuIbhGIXykyNKiJh+KrLweSBajVHHitPLyoY= -sigs.k8s.io/controller-runtime v0.19.2 h1:3sPrF58XQEPzbE8T81TN6selQIMGbtYwuaJ6eDssDF8= -sigs.k8s.io/controller-runtime v0.19.2/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= sigs.k8s.io/controller-runtime v0.19.3 h1:XO2GvC9OPftRst6xWCpTgBZO04S2cbp0Qqkj8bX1sPw= sigs.k8s.io/controller-runtime v0.19.3/go.mod h1:j4j87DqtsThvwTv5/Tc5NFRyyF/RF0ip4+62tbTSIUM= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= diff --git a/lib/notifications/export_test.go b/lib/notifications/export_test.go index dbb48130..3fff186d 100644 --- a/lib/notifications/export_test.go +++ b/lib/notifications/export_test.go @@ -1,9 +1,30 @@ package notifications +import ( + "fmt" + + "github.com/go-resty/resty/v2" + webexteams "github.com/jbogarin/go-cisco-webex-teams/sdk" +) + var ( - GetSmtpInfo = getSmtpInfo + GetSmtpInfo = getSmtpInfo + GetWebexInfo = getWebexInfo ) var ExtractSmtpConfiguration = func(info *smtpInfo) (string, string, string, string, string, string, string) { return info.recipients, info.bcc, info.identity, info.fromEmail, info.password, info.host, info.port } + +var ExtractWebexConfiguration = func(info *webexInfo) (string, string) { + return info.room, info.token +} + +var MockCreateMessage = func(err bool) { + sendWebexMessage = func(wc *webexteams.Client, message *webexteams.MessageCreateRequest) (*webexteams.Message, *resty.Response, error) { + if err { + return nil, nil, fmt.Errorf("failed to send message") + } + return nil, nil, nil + } +} diff --git a/lib/notifications/notifications.go b/lib/notifications/notifications.go index d1194d2d..2cb5c809 100644 --- a/lib/notifications/notifications.go +++ b/lib/notifications/notifications.go @@ -13,15 +13,15 @@ import ( func getSecret(ctx context.Context, c client.Client, notification *v1beta1.Notification) (*corev1.Secret, error) { if notification.NotificationRef == nil { - return nil, fmt.Errorf("notification must reference v1 secret containing smtp configuration") + return nil, fmt.Errorf("notification must reference v1 secret containing notification configuration") } if notification.NotificationRef.Kind != "Secret" { - return nil, fmt.Errorf("notification must reference v1 secret containing smtp configuration") + return nil, fmt.Errorf("notification must reference v1 secret containing notification configuration") } if notification.NotificationRef.APIVersion != "v1" { - return nil, fmt.Errorf("notification must reference v1 secret containing smtp configuration") + return nil, fmt.Errorf("notification must reference v1 secret containing notification configuration") } secret := &corev1.Secret{} @@ -34,7 +34,7 @@ func getSecret(ctx context.Context, c client.Client, notification *v1beta1.Notif } if secret.Data == nil { - return nil, fmt.Errorf("notification must reference v1 secret containing smtp configuration") + return nil, fmt.Errorf("notification must reference v1 secret containing notification configuration") } return secret, nil diff --git a/lib/notifications/notifications_suite_test.go b/lib/notifications/notifications_suite_test.go index 72dba6e4..944cba64 100644 --- a/lib/notifications/notifications_suite_test.go +++ b/lib/notifications/notifications_suite_test.go @@ -22,6 +22,9 @@ import ( "testing" "time" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -119,3 +122,27 @@ func waitForObject(ctx context.Context, c client.Client, obj client.Object) erro } return nil } + +func createNamespaceAndSecret(data map[string][]byte) (namespaceName, secretName string) { + namespaceName = randomString() + secretName = randomString() + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespaceName, + }, + } + Expect(k8sClient.Create(context.TODO(), ns)).To(Succeed()) + Expect(waitForObject(context.TODO(), k8sClient, ns)).To(Succeed()) + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: namespaceName, + }, + Data: data, + } + + Expect(k8sClient.Create(context.TODO(), secret)).To(Succeed()) + Expect(waitForObject(context.TODO(), k8sClient, secret)).To(Succeed()) + return +} diff --git a/lib/notifications/notifications_test.go b/lib/notifications/notifications_test.go index 7475d02d..5aab0553 100644 --- a/lib/notifications/notifications_test.go +++ b/lib/notifications/notifications_test.go @@ -6,11 +6,12 @@ import ( "strings" "time" + "github.com/go-logr/logr" + smtpmock "github.com/mocktools/go-smtp-mock/v2" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/rand" libsveltosv1beta1 "github.com/projectsveltos/libsveltos/api/v1beta1" @@ -27,30 +28,17 @@ var _ = Describe("Notification", func() { smtpHost := fmt.Sprintf("%s.com", randomString()) smtpPort := rand.IntnRange(444, 9999) - secretNamespace := randomString() - secretNs := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretNamespace, - }, + data := map[string][]byte{ + libsveltosv1beta1.SmtpRecipients: []byte(smtpRecipients), + libsveltosv1beta1.SmtpBcc: []byte(smtpBcc), + libsveltosv1beta1.SmtpIdentity: []byte(smtpIdentity), + libsveltosv1beta1.SmtpSender: []byte(smtpSender), + libsveltosv1beta1.SmtpPassword: []byte(smtpPassword), + libsveltosv1beta1.SmtpHost: []byte(smtpHost), + libsveltosv1beta1.SmtpPort: []byte(fmt.Sprintf("%d", smtpPort)), } - Expect(k8sClient.Create(context.TODO(), secretNs)).To(Succeed()) - Expect(waitForObject(context.TODO(), k8sClient, secretNs)).To(Succeed()) - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: randomString(), - Namespace: secretNamespace, - }, - Data: map[string][]byte{ - libsveltosv1beta1.SmtpRecipients: []byte(smtpRecipients), - libsveltosv1beta1.SmtpBcc: []byte(smtpBcc), - libsveltosv1beta1.SmtpIdentity: []byte(smtpIdentity), - libsveltosv1beta1.SmtpSender: []byte(smtpSender), - libsveltosv1beta1.SmtpPassword: []byte(smtpPassword), - libsveltosv1beta1.SmtpHost: []byte(smtpHost), - libsveltosv1beta1.SmtpPort: []byte(fmt.Sprintf("%d", smtpPort)), - }, - } + ns, sec := createNamespaceAndSecret(data) notification := &libsveltosv1beta1.Notification{ Name: randomString(), @@ -58,14 +46,11 @@ var _ = Describe("Notification", func() { NotificationRef: &corev1.ObjectReference{ Kind: "Secret", APIVersion: "v1", - Namespace: secret.Namespace, - Name: secret.Name, + Namespace: ns, + Name: sec, }, } - Expect(k8sClient.Create(context.TODO(), secret)).To(Succeed()) - Expect(waitForObject(context.TODO(), k8sClient, secret)).To(Succeed()) - smptInfo, err := notifications.GetSmtpInfo(context.TODO(), k8sClient, notification) Expect(err).To(BeNil()) Expect(smptInfo).ToNot(BeNil()) @@ -80,21 +65,8 @@ var _ = Describe("Notification", func() { Expect(port).To(Equal(fmt.Sprintf("%d", smtpPort))) }) It("getSmtpInfo raises exception if Secret Data is nil", func() { - secretNamespace := randomString() - secretNs := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretNamespace, - }, - } - Expect(k8sClient.Create(context.TODO(), secretNs)).To(Succeed()) - Expect(waitForObject(context.TODO(), k8sClient, secretNs)).To(Succeed()) - - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: randomString(), - Namespace: secretNamespace, - }, - } + data := map[string][]byte{} + ns, sec := createNamespaceAndSecret(data) notification := &libsveltosv1beta1.Notification{ Name: randomString(), @@ -102,18 +74,15 @@ var _ = Describe("Notification", func() { NotificationRef: &corev1.ObjectReference{ Kind: "Secret", APIVersion: "v1", - Namespace: secret.Namespace, - Name: secret.Name, + Namespace: ns, + Name: sec, }, } - Expect(k8sClient.Create(context.TODO(), secret)).To(Succeed()) - Expect(waitForObject(context.TODO(), k8sClient, secret)).To(Succeed()) - smptInfo, err := notifications.GetSmtpInfo(context.TODO(), k8sClient, notification) Expect(smptInfo).To(BeNil()) Expect(err).ToNot(BeNil()) - Expect(err).To(Equal(fmt.Errorf("notification must reference v1 secret containing smtp configuration"))) + Expect(err).To(Equal(fmt.Errorf("notification must reference v1 secret containing notification configuration"))) }) It("getSmtpInfo raises exception if NotificationRef is nil", func() { notification := &libsveltosv1beta1.Notification{ @@ -123,89 +92,55 @@ var _ = Describe("Notification", func() { _, err := notifications.GetSmtpInfo(context.TODO(), k8sClient, notification) Expect(err).ToNot(BeNil()) - Expect(err).To(Equal(fmt.Errorf("notification must reference v1 secret containing smtp configuration"))) + Expect(err).To(Equal(fmt.Errorf("notification must reference v1 secret containing notification configuration"))) }) It("NewMailer creates a new mailer", func() { - secretNamespace := randomString() - secretNs := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretNamespace, - }, - } - Expect(k8sClient.Create(context.TODO(), secretNs)).To(Succeed()) - Expect(waitForObject(context.TODO(), k8sClient, secretNs)).To(Succeed()) + smtpRecipients := fmt.Sprintf("%s@a.com,%s@b.com", randomString(), randomString()) + smtpBcc := fmt.Sprintf("%s@c.com", randomString()) + smtpIdentity := randomString() + smtpSender := fmt.Sprintf("%s@d.com", randomString()) + smtpPassword := randomString() + smtpHost := fmt.Sprintf("%s.com", randomString()) + smtpPort := rand.IntnRange(444, 9999) - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: randomString(), - Namespace: secretNamespace, - }, - Data: map[string][]byte{ - libsveltosv1beta1.SmtpRecipients: []byte(fmt.Sprintf("%s@a.com,%s@b.com", randomString(), randomString())), - libsveltosv1beta1.SmtpBcc: []byte(fmt.Sprintf("%s@c.com", randomString())), - libsveltosv1beta1.SmtpIdentity: []byte(randomString()), - libsveltosv1beta1.SmtpSender: []byte(fmt.Sprintf("%s@d.com", randomString())), - libsveltosv1beta1.SmtpPassword: []byte(randomString()), - libsveltosv1beta1.SmtpHost: []byte(fmt.Sprintf("%s.com", randomString())), - libsveltosv1beta1.SmtpPort: []byte(fmt.Sprintf("%d", rand.IntnRange(444, 9999))), - }, + data := map[string][]byte{ + libsveltosv1beta1.SmtpRecipients: []byte(smtpRecipients), + libsveltosv1beta1.SmtpBcc: []byte(smtpBcc), + libsveltosv1beta1.SmtpIdentity: []byte(smtpIdentity), + libsveltosv1beta1.SmtpSender: []byte(smtpSender), + libsveltosv1beta1.SmtpPassword: []byte(smtpPassword), + libsveltosv1beta1.SmtpHost: []byte(smtpHost), + libsveltosv1beta1.SmtpPort: []byte(fmt.Sprintf("%d", smtpPort)), } + ns, sec := createNamespaceAndSecret(data) + notification := &libsveltosv1beta1.Notification{ Name: randomString(), Type: libsveltosv1beta1.NotificationTypeSMTP, NotificationRef: &corev1.ObjectReference{ Kind: "Secret", APIVersion: "v1", - Namespace: secret.Namespace, - Name: secret.Name, + Namespace: ns, + Name: sec, }, } - Expect(k8sClient.Create(context.TODO(), secret)).To(Succeed()) - Expect(waitForObject(context.TODO(), k8sClient, secret)).To(Succeed()) - mailer, err := notifications.NewMailer(context.Background(), k8sClient, notification) Expect(err).To(BeNil()) Expect(mailer).ToNot(BeNil()) }) It("NewMailer raises exception if notification is nil", func() { - secretNamespace := randomString() - secretNs := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretNamespace, - }, - } - Expect(k8sClient.Create(context.TODO(), secretNs)).To(Succeed()) - Expect(waitForObject(context.TODO(), k8sClient, secretNs)).To(Succeed()) - - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: randomString(), - Namespace: secretNamespace, - }, - Data: map[string][]byte{ - libsveltosv1beta1.SmtpRecipients: []byte(fmt.Sprintf("%s@a.com,%s@b.com", randomString(), randomString())), - libsveltosv1beta1.SmtpBcc: []byte(fmt.Sprintf("%s@c.com", randomString())), - libsveltosv1beta1.SmtpIdentity: []byte(randomString()), - libsveltosv1beta1.SmtpSender: []byte(fmt.Sprintf("%s@d.com", randomString())), - libsveltosv1beta1.SmtpPassword: []byte(randomString()), - libsveltosv1beta1.SmtpHost: []byte(fmt.Sprintf("%s.com", randomString())), - libsveltosv1beta1.SmtpPort: []byte(fmt.Sprintf("%d", rand.IntnRange(444, 9999))), - }, - } - - Expect(k8sClient.Create(context.TODO(), secret)).To(Succeed()) - Expect(waitForObject(context.TODO(), k8sClient, secret)).To(Succeed()) - mailer, err := notifications.NewMailer(context.Background(), k8sClient, &libsveltosv1beta1.Notification{}) Expect(mailer).To(BeNil()) Expect(err).ToNot(BeNil()) - Expect(err).To(Equal(fmt.Errorf("could not create mailer, %w", fmt.Errorf("notification must reference v1 secret containing smtp configuration")))) + Expect(err).To(Equal(fmt.Errorf("could not create mailer, %w", fmt.Errorf("notification must reference v1 secret containing notification configuration")))) }) It("SendMail sends mail successfully", func() { smtpHost := "127.0.0.1" smtpPort := 2525 + smtpRecipients := fmt.Sprintf("%s@a.com,%s@b.com", randomString(), randomString()) + smtpSender := fmt.Sprintf("%s@d.com", randomString()) // test server does not support auth emailSubject := "Test Subject" plainEmailMessage := "Test Message" @@ -219,27 +154,14 @@ var _ = Describe("Notification", func() { Fail(fmt.Sprintf("failed to start smtp server: %v", err)) } - secretNamespace := randomString() - secretNs := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretNamespace, - }, + data := map[string][]byte{ + libsveltosv1beta1.SmtpRecipients: []byte(smtpRecipients), + libsveltosv1beta1.SmtpSender: []byte(smtpSender), + libsveltosv1beta1.SmtpHost: []byte(smtpHost), + libsveltosv1beta1.SmtpPort: []byte(fmt.Sprintf("%d", smtpPort)), } - Expect(k8sClient.Create(context.TODO(), secretNs)).To(Succeed()) - Expect(waitForObject(context.TODO(), k8sClient, secretNs)).To(Succeed()) - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: randomString(), - Namespace: secretNamespace, - }, - Data: map[string][]byte{ - libsveltosv1beta1.SmtpRecipients: []byte(fmt.Sprintf("%s@a.com,%s@b.com", randomString(), randomString())), - libsveltosv1beta1.SmtpSender: []byte(fmt.Sprintf("%s@d.com", randomString())), - libsveltosv1beta1.SmtpHost: []byte(smtpHost), - libsveltosv1beta1.SmtpPort: []byte(fmt.Sprintf("%d", smtpPort)), - }, - } + ns, sec := createNamespaceAndSecret(data) notification := &libsveltosv1beta1.Notification{ Name: randomString(), @@ -247,14 +169,11 @@ var _ = Describe("Notification", func() { NotificationRef: &corev1.ObjectReference{ Kind: "Secret", APIVersion: "v1", - Namespace: secret.Namespace, - Name: secret.Name, + Namespace: ns, + Name: sec, }, } - Expect(k8sClient.Create(context.TODO(), secret)).To(Succeed()) - Expect(waitForObject(context.TODO(), k8sClient, secret)).To(Succeed()) - mailer, err := notifications.NewMailer(context.Background(), k8sClient, notification) Expect(err).To(BeNil()) Expect(mailer).ToNot(BeNil()) @@ -292,4 +211,117 @@ var _ = Describe("Notification", func() { Fail(fmt.Sprintf("failed to stop smtp server: %v", err)) } }) + It("getWebexInfo gets info from secret", func() { + webexRoom := randomString() + webexToken := randomString() + + data := map[string][]byte{ + libsveltosv1beta1.WebexRoomID: []byte(webexRoom), + libsveltosv1beta1.WebexToken: []byte(webexToken), + } + + ns, sec := createNamespaceAndSecret(data) + + notification := &libsveltosv1beta1.Notification{ + Name: randomString(), + Type: libsveltosv1beta1.NotificationTypeSMTP, + NotificationRef: &corev1.ObjectReference{ + Kind: "Secret", + APIVersion: "v1", + Namespace: ns, + Name: sec, + }, + } + + webexInfo, err := notifications.GetWebexInfo(context.TODO(), k8sClient, notification) + Expect(err).To(BeNil()) + Expect(webexInfo).ToNot(BeNil()) + + room, token := notifications.ExtractWebexConfiguration(webexInfo) + Expect(room).To(Equal(webexRoom)) + Expect(token).To(Equal(webexToken)) + }) + It("getWebexInfo raises exception if Secret Data is nil", func() { + data := map[string][]byte{} + ns, sec := createNamespaceAndSecret(data) + + notification := &libsveltosv1beta1.Notification{ + Name: randomString(), + Type: libsveltosv1beta1.NotificationTypeWebex, + NotificationRef: &corev1.ObjectReference{ + Kind: "Secret", + APIVersion: "v1", + Namespace: ns, + Name: sec, + }, + } + + webexInfo, err := notifications.GetWebexInfo(context.TODO(), k8sClient, notification) + Expect(webexInfo).To(BeNil()) + Expect(err).ToNot(BeNil()) + Expect(err).To(Equal(fmt.Errorf("notification must reference v1 secret containing notification configuration"))) + }) + It("SendWebexMessage sends message successfully", func() { + webexRoom := randomString() + webexToken := randomString() + + data := map[string][]byte{ + libsveltosv1beta1.WebexRoomID: []byte(webexRoom), + libsveltosv1beta1.WebexToken: []byte(webexToken), + } + + ns, sec := createNamespaceAndSecret(data) + + notification := &libsveltosv1beta1.Notification{ + Name: randomString(), + Type: libsveltosv1beta1.NotificationTypeWebex, + NotificationRef: &corev1.ObjectReference{ + Kind: "Secret", + APIVersion: "v1", + Namespace: ns, + Name: sec, + }, + } + + notifier, err := notifications.NewWebexNotifier(context.TODO(), k8sClient, notification) + Expect(err).To(BeNil()) + Expect(notifier).ToNot(BeNil()) + + notifications.MockCreateMessage(false) + + err = notifier.SendNotification("Test Message", nil, logr.New(nil)) + Expect(err).To(BeNil()) + }) + It("SendWebexMessage raises error if message was not sent successfully", func() { + webexRoom := randomString() + webexToken := randomString() + + data := map[string][]byte{ + libsveltosv1beta1.WebexRoomID: []byte(webexRoom), + libsveltosv1beta1.WebexToken: []byte(webexToken), + } + + ns, sec := createNamespaceAndSecret(data) + + notification := &libsveltosv1beta1.Notification{ + Name: randomString(), + Type: libsveltosv1beta1.NotificationTypeWebex, + NotificationRef: &corev1.ObjectReference{ + Kind: "Secret", + APIVersion: "v1", + Namespace: ns, + Name: sec, + }, + } + + notifier, err := notifications.NewWebexNotifier(context.TODO(), k8sClient, notification) + Expect(err).To(BeNil()) + Expect(notifier).ToNot(BeNil()) + + notifications.MockCreateMessage(true) + + err = notifier.SendNotification("Test Message", nil, logr.New(nil)) + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(Equal("failed to send message")) + }) }) diff --git a/lib/notifications/webex.go b/lib/notifications/webex.go new file mode 100644 index 00000000..cc019c99 --- /dev/null +++ b/lib/notifications/webex.go @@ -0,0 +1,88 @@ +package notifications + +import ( + "context" + "fmt" + + "github.com/go-resty/resty/v2" + + "github.com/go-logr/logr" + webexteams "github.com/jbogarin/go-cisco-webex-teams/sdk" + "sigs.k8s.io/controller-runtime/pkg/client" + + libsveltosv1beta1 "github.com/projectsveltos/libsveltos/api/v1beta1" + logs "github.com/projectsveltos/libsveltos/lib/logsettings" +) + +type webexInfo struct { + token string + room string +} + +type WebexNotifier struct { + webexInfo *webexInfo +} + +var ( + sendWebexMessage = func(wc *webexteams.Client, message *webexteams.MessageCreateRequest) (*webexteams.Message, *resty.Response, error) { + return wc.Messages.CreateMessage(message) + } +) + +func NewWebexNotifier(ctx context.Context, c client.Client, notification *libsveltosv1beta1.Notification) (*WebexNotifier, error) { + info, err := getWebexInfo(ctx, c, notification) + if err != nil { + return nil, err + } + return &WebexNotifier{webexInfo: info}, nil +} + +func (wn *WebexNotifier) SendNotification(message string, files []webexteams.File, logger logr.Logger) error { + l := logger.WithValues("room", wn.webexInfo.room) + l.V(logs.LogInfo).Info("send webex message") + + webexClient := webexteams.NewClient() + if webexClient == nil { + l.V(logs.LogInfo).Info("failed to get webexClient client") + return fmt.Errorf("failed to get webexClient client") + } + webexClient.SetAuthToken(wn.webexInfo.token) + + webexMessage := &webexteams.MessageCreateRequest{ + Markdown: message, + RoomID: wn.webexInfo.room, + Files: files, + } + + _, resp, err := sendWebexMessage(webexClient, webexMessage) + + if err != nil { + l.V(logs.LogInfo).Info(fmt.Sprintf("Failed to send message. Error: %v", err)) + return err + } + + if resp != nil { + l.V(logs.LogDebug).Info(fmt.Sprintf("response: %s", string(resp.Body()))) + } + + return nil +} + +func getWebexInfo(ctx context.Context, c client.Client, notification *libsveltosv1beta1.Notification) (*webexInfo, error) { + secret, err := getSecret(ctx, c, notification) + if err != nil { + return nil, err + } + + authToken, ok := secret.Data[libsveltosv1beta1.WebexToken] + if !ok { + return nil, fmt.Errorf("secret does not contain webex token") + } + + room, ok := secret.Data[libsveltosv1beta1.WebexRoomID] + if !ok { + return nil, fmt.Errorf("secret does not contain webex room") + } + + return &webexInfo{token: string(authToken), room: string(room)}, nil +}