Skip to content

Commit

Permalink
feat(notifier): add validation notifier config (#1099)
Browse files Browse the repository at this point in the history
  • Loading branch information
almostinf authored Oct 7, 2024
1 parent 1fd8c2f commit 8255890
Show file tree
Hide file tree
Showing 33 changed files with 381 additions and 177 deletions.
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ require (
require github.com/prometheus/common v0.37.0

require (
github.com/go-playground/validator/v10 v10.4.1
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/mattermost/mattermost/server/public v0.1.1
github.com/mitchellh/mapstructure v1.5.0
Expand Down Expand Up @@ -183,12 +184,15 @@ require (
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/spec v0.20.9 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-playground/locales v0.13.0 // indirect
github.com/go-playground/universal-translator v0.17.0 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-plugin v1.6.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.11 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/leodido/go-urn v1.2.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -637,9 +637,13 @@ github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
Expand Down Expand Up @@ -898,6 +902,7 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
Expand Down
8 changes: 8 additions & 0 deletions helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"math"
"strings"
"time"

"github.com/go-playground/validator/v10"
)

// BytesScanner allows to scan for subslices separated by separator.
Expand Down Expand Up @@ -250,3 +252,9 @@ func MergeToSorted[T Comparable](arr1, arr2 []T) ([]T, error) {

return merged, nil
}

// ValidateStruct is a default generic function that uses a validator to validate structure fields.
func ValidateStruct(s any) error {
validator := validator.New()
return validator.Struct(s)
}
56 changes: 51 additions & 5 deletions helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,25 +290,25 @@ func TestMergeToSorted(t *testing.T) {
})

Convey("Test with one nil array", func() {
merged, err := MergeToSorted[myInt](nil, []myInt{1, 2, 3})
merged, err := MergeToSorted(nil, []myInt{1, 2, 3})
So(err, ShouldBeNil)
So(merged, ShouldResemble, []myInt{1, 2, 3})
})

Convey("Test with two arrays", func() {
merged, err := MergeToSorted[myInt]([]myInt{4, 5}, []myInt{1, 2, 3})
merged, err := MergeToSorted([]myInt{4, 5}, []myInt{1, 2, 3})
So(err, ShouldBeNil)
So(merged, ShouldResemble, []myInt{1, 2, 3, 4, 5})
})

Convey("Test with empty array", func() {
merged, err := MergeToSorted[myInt]([]myInt{-4, 5}, []myInt{})
merged, err := MergeToSorted([]myInt{-4, 5}, []myInt{})
So(err, ShouldBeNil)
So(merged, ShouldResemble, []myInt{-4, 5})
})

Convey("Test with sorted values but mixed up", func() {
merged, err := MergeToSorted[myInt]([]myInt{1, 9, 10}, []myInt{4, 8, 12})
merged, err := MergeToSorted([]myInt{1, 9, 10}, []myInt{4, 8, 12})
So(err, ShouldBeNil)
So(merged, ShouldResemble, []myInt{1, 4, 8, 9, 10, 12})
})
Expand All @@ -333,9 +333,55 @@ func TestMergeToSorted(t *testing.T) {
}

expected := append(arr2, arr1...)
merged, err := MergeToSorted[myTest](arr1, arr2)
merged, err := MergeToSorted(arr1, arr2)
So(err, ShouldBeNil)
So(merged, ShouldResemble, expected)
})
})
}

func TestValidateStruct(t *testing.T) {
type ValidationStruct struct {
TestInt int `validate:"required,gt=0"`
TestURL string `validate:"required,url"`
TestBool bool
}

const (
validURL = "https://github.com/moira-alert/moira"
validInt = 1
)

Convey("Test ValidateStruct", t, func() {
Convey("With TestInt less than zero", func() {
testStruct := ValidationStruct{
TestInt: -1,
TestURL: validURL,
}

err := ValidateStruct(testStruct)
So(err, ShouldNotBeNil)
})

Convey("With invalid TestURL format", func() {
testStruct := ValidationStruct{
TestInt: validInt,
TestURL: "test",
TestBool: true,
}

err := ValidateStruct(testStruct)
So(err, ShouldNotBeNil)
})

Convey("With valid structure", func() {
testStruct := ValidationStruct{
TestInt: validInt,
TestURL: validURL,
}

err := ValidateStruct(testStruct)
So(err, ShouldBeNil)
})
})
}
7 changes: 4 additions & 3 deletions senders/discord/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const (
// Structure that represents the Discord configuration in the YAML file.
type config struct {
ContactType string `mapstructure:"contact_type"`
Token string `mapstructure:"token"`
Token string `mapstructure:"token" validate:"required"`
FrontURI string `mapstructure:"front_uri"`
}

Expand All @@ -42,9 +42,10 @@ func (sender *Sender) Init(senderSettings interface{}, logger moira.Logger, loca
return fmt.Errorf("failed to decode senderSettings to discord config: %w", err)
}

if cfg.Token == "" {
return fmt.Errorf("cannot read the discord token from the config")
if err = moira.ValidateStruct(cfg); err != nil {
return fmt.Errorf("discord config validation error: %w", err)
}

sender.session, err = discordgo.New("Bot " + cfg.Token)
if err != nil {
return fmt.Errorf("error creating discord session: %w", err)
Expand Down
18 changes: 13 additions & 5 deletions senders/discord/init_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package discord

import (
"fmt"
"errors"
"testing"
"time"

"github.com/go-playground/validator/v10"
"github.com/moira-alert/moira"

logging "github.com/moira-alert/moira/logging/zerolog_adapter"
Expand All @@ -31,9 +32,14 @@ func TestInit(t *testing.T) {
location, _ := time.LoadLocation("UTC")
Convey("Init tests", t, func() {
sender := Sender{DataBase: &MockDB{}}
Convey("Empty map", func() {
err := sender.Init(map[string]interface{}{}, logger, nil, "")
So(err, ShouldResemble, fmt.Errorf("cannot read the discord token from the config"))

validatorErr := validator.ValidationErrors{}

Convey("With empty token", func() {
senderSettings := map[string]interface{}{}

err := sender.Init(senderSettings, logger, nil, "")
So(errors.As(err, &validatorErr), ShouldBeTrue)
So(sender, ShouldResemble, Sender{DataBase: &MockDB{}})
})

Expand All @@ -42,7 +48,9 @@ func TestInit(t *testing.T) {
"token": "123",
"front_uri": "http://moira.uri",
}
sender.Init(senderSettings, logger, location, "15:04") //nolint

err := sender.Init(senderSettings, logger, location, "15:04") //nolint
So(err, ShouldBeNil)
So(sender.frontURI, ShouldResemble, "http://moira.uri")
So(sender.session.Token, ShouldResemble, "Bot 123")
So(sender.logger, ShouldResemble, logger)
Expand Down
18 changes: 12 additions & 6 deletions senders/mail/mail.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import (

// Structure that represents the Mail configuration in the YAML file.
type config struct {
MailFrom string `mapstructure:"mail_from"`
MailFrom string `mapstructure:"mail_from" validate:"required"`
SMTPHello string `mapstructure:"smtp_hello"`
SMTPHost string `mapstructure:"smtp_host"`
SMTPPort int64 `mapstructure:"smtp_port"`
SMTPHost string `mapstructure:"smtp_host" validate:"required"`
SMTPPort int64 `mapstructure:"smtp_port" validate:"required"`
InsecureTLS bool `mapstructure:"insecure_tls"`
FrontURI string `mapstructure:"front_uri"`
SMTPPass string `mapstructure:"smtp_pass"`
Expand Down Expand Up @@ -64,6 +64,10 @@ func (sender *Sender) fillSettings(senderSettings interface{}, logger moira.Logg
return fmt.Errorf("failed to decode senderSettings to mail config: %w", err)
}

if err = moira.ValidateStruct(cfg); err != nil {
return fmt.Errorf("mail config validation error: %w", err)
}

sender.logger = logger
sender.From = cfg.MailFrom
sender.SMTPHello = cfg.SMTPHello
Expand All @@ -76,12 +80,11 @@ func (sender *Sender) fillSettings(senderSettings interface{}, logger moira.Logg
sender.TemplateFile = cfg.TemplateFile
sender.location = location
sender.dateTimeFormat = dateTimeFormat

if sender.Username == "" {
sender.Username = sender.From
}
if sender.From == "" {
return fmt.Errorf("mail_from can't be empty")
}

return nil
}

Expand All @@ -106,11 +109,13 @@ func (sender *Sender) tryDial() error {
return err
}
defer t.Close()

if sender.SMTPHello != "" {
if err := t.Hello(sender.SMTPHello); err != nil {
return err
}
}

if sender.Password != "" {
tlsConfig := &tls.Config{
InsecureSkipVerify: sender.InsecureTLS,
Expand All @@ -123,5 +128,6 @@ func (sender *Sender) tryDial() error {
return err
}
}

return nil
}
91 changes: 74 additions & 17 deletions senders/mail/mail_test.go
Original file line number Diff line number Diff line change
@@ -1,33 +1,90 @@
package mail

import (
"fmt"
"errors"
"testing"

"github.com/go-playground/validator/v10"
. "github.com/smartystreets/goconvey/convey"
)

const (
defaultMailFrom = "test-mail-from"
defaultSMTPHost = "test-smtp-host"
defaultSMTPPort = 80
defaultSMTPHello = "test-smtp-hello"
defaultInsecureTLS = true
defaultFrontURI = "test-front-uri"
defaultSMTPPass = "test-smtp-pass"
defaultSMTPUser = "test-smtp-user"
defaultTemplateFile = "test-template-file"
)

func TestFillSettings(t *testing.T) {
Convey("Empty map", t, func() {
Convey("Test fillSettings", t, func() {
sender := Sender{}
err := sender.fillSettings(map[string]interface{}{}, nil, nil, "")
So(err, ShouldResemble, fmt.Errorf("mail_from can't be empty"))
So(sender, ShouldResemble, Sender{})
})

Convey("Has From", t, func() {
sender := Sender{}
settings := map[string]interface{}{"mail_from": "123"}
Convey("No username", func() {
err := sender.fillSettings(settings, nil, nil, "")
So(err, ShouldBeNil)
So(sender, ShouldResemble, Sender{From: "123", Username: "123"})
validatorErr := validator.ValidationErrors{}

Convey("With empty mail_from", func() {
senderSettings := map[string]interface{}{
"smtp_host": defaultSMTPHost,
"smtp_port": defaultSMTPPort,
}

err := sender.fillSettings(senderSettings, nil, nil, "")
So(errors.As(err, &validatorErr), ShouldBeTrue)
So(sender, ShouldResemble, Sender{})
})
Convey("Has username", func() {
settings["smtp_user"] = "user"
err := sender.fillSettings(settings, nil, nil, "")

Convey("With empty smpt_host", func() {
senderSettings := map[string]interface{}{
"mail_from": defaultMailFrom,
"smtp_port": defaultSMTPPort,
}

err := sender.fillSettings(senderSettings, nil, nil, "")
So(errors.As(err, &validatorErr), ShouldBeTrue)
So(sender, ShouldResemble, Sender{})
})

Convey("With empty smpt_port", func() {
senderSettings := map[string]interface{}{
"mail_from": defaultMailFrom,
"smtp_host": defaultSMTPHost,
}

err := sender.fillSettings(senderSettings, nil, nil, "")
So(errors.As(err, &validatorErr), ShouldBeTrue)
So(sender, ShouldResemble, Sender{})
})

Convey("With full settings", func() {
senderSettings := map[string]interface{}{
"mail_from": defaultMailFrom,
"smtp_host": defaultSMTPHost,
"smtp_port": defaultSMTPPort,
"smtp_hello": defaultSMTPHello,
"insecure_tls": defaultInsecureTLS,
"front_uri": defaultFrontURI,
"smtp_user": defaultSMTPUser,
"smtp_pass": defaultSMTPPass,
"template_file": defaultTemplateFile,
}

err := sender.fillSettings(senderSettings, nil, nil, "")
So(err, ShouldBeNil)
So(sender, ShouldResemble, Sender{From: "123", Username: "user"})
So(sender, ShouldResemble, Sender{
From: defaultMailFrom,
SMTPHello: defaultSMTPHello,
SMTPHost: defaultSMTPHost,
SMTPPort: 80,
FrontURI: defaultFrontURI,
InsecureTLS: defaultInsecureTLS,
Username: defaultSMTPUser,
Password: defaultSMTPPass,
TemplateFile: defaultTemplateFile,
})
})
})
}
Expand Down
Loading

0 comments on commit 8255890

Please sign in to comment.