From b11f2cce88b33cf960327ee10e4aa4fcfd720d97 Mon Sep 17 00:00:00 2001 From: ULBRICR Date: Mon, 1 Jul 2024 09:39:21 +0200 Subject: [PATCH 01/28] Introduced SMTP notification Introduced SMTP notification for Flyte Signed-off-by: Rob Ulbrich --- flyteadmin/go.mod | 2 +- flyteadmin/pkg/async/notifications/factory.go | 13 +- .../pkg/async/notifications/factory_test.go | 5 +- .../notifications/implementations/emailers.go | 1 + .../implementations/smtp_emailer.go | 143 ++++++++++++++++++ .../implementations/smtp_emailer_test.go | 60 ++++++++ flyteadmin/pkg/rpc/adminservice/base.go | 5 +- .../interfaces/application_configuration.go | 9 +- flyteadmin/pkg/server/service.go | 17 ++- 9 files changed, 237 insertions(+), 18 deletions(-) create mode 100644 flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go create mode 100644 flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go diff --git a/flyteadmin/go.mod b/flyteadmin/go.mod index d3e5e602d7..cf5de6c260 100644 --- a/flyteadmin/go.mod +++ b/flyteadmin/go.mod @@ -50,6 +50,7 @@ require ( github.com/wI2L/jsondiff v0.5.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 go.opentelemetry.io/otel v1.24.0 + golang.org/x/net v0.23.0 golang.org/x/oauth2 v0.16.0 golang.org/x/time v0.5.0 google.golang.org/api v0.155.0 @@ -188,7 +189,6 @@ require ( go.opentelemetry.io/proto/otlp v1.1.0 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect - golang.org/x/net v0.23.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/term v0.18.0 // indirect diff --git a/flyteadmin/pkg/async/notifications/factory.go b/flyteadmin/pkg/async/notifications/factory.go index f94129a1d5..1a49340908 100644 --- a/flyteadmin/pkg/async/notifications/factory.go +++ b/flyteadmin/pkg/async/notifications/factory.go @@ -3,6 +3,7 @@ package notifications import ( "context" "fmt" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" "sync" "time" @@ -50,13 +51,15 @@ func CreateMsgChan() { }) } -func GetEmailer(config runtimeInterfaces.NotificationsConfig, scope promutils.Scope) interfaces.Emailer { +func GetEmailer(config runtimeInterfaces.NotificationsConfig, scope promutils.Scope, sm core.SecretManager) interfaces.Emailer { // If an external email service is specified use that instead. // TODO: Handling of this is messy, see https://github.com/flyteorg/flyte/issues/1063 if config.NotificationsEmailerConfig.EmailerConfig.ServiceName != "" { switch config.NotificationsEmailerConfig.EmailerConfig.ServiceName { case implementations.Sendgrid: return implementations.NewSendGridEmailer(config, scope) + case implementations.Smtp: + return implementations.NewSmtpEmailer(context.Background(), config, scope, sm) default: panic(fmt.Errorf("No matching email implementation for %s", config.NotificationsEmailerConfig.EmailerConfig.ServiceName)) } @@ -87,7 +90,7 @@ func GetEmailer(config runtimeInterfaces.NotificationsConfig, scope promutils.Sc } } -func NewNotificationsProcessor(config runtimeInterfaces.NotificationsConfig, scope promutils.Scope) interfaces.Processor { +func NewNotificationsProcessor(config runtimeInterfaces.NotificationsConfig, scope promutils.Scope, sm core.SecretManager) interfaces.Processor { reconnectAttempts := config.ReconnectAttempts reconnectDelay := time.Duration(config.ReconnectDelaySeconds) * time.Second var sub pubsub.Subscriber @@ -119,7 +122,7 @@ func NewNotificationsProcessor(config runtimeInterfaces.NotificationsConfig, sco if err != nil { panic(err) } - emailer = GetEmailer(config, scope) + emailer = GetEmailer(config, scope, sm) return implementations.NewProcessor(sub, emailer, scope) case common.GCP: projectID := config.GCPConfig.ProjectID @@ -135,10 +138,10 @@ func NewNotificationsProcessor(config runtimeInterfaces.NotificationsConfig, sco if err != nil { panic(err) } - emailer = GetEmailer(config, scope) + emailer = GetEmailer(config, scope, sm) return implementations.NewGcpProcessor(sub, emailer, scope) case common.Sandbox: - emailer = GetEmailer(config, scope) + emailer = GetEmailer(config, scope, sm) return implementations.NewSandboxProcessor(msgChan, emailer) case common.Local: fallthrough diff --git a/flyteadmin/pkg/async/notifications/factory_test.go b/flyteadmin/pkg/async/notifications/factory_test.go index 1bfd1f4596..3505705766 100644 --- a/flyteadmin/pkg/async/notifications/factory_test.go +++ b/flyteadmin/pkg/async/notifications/factory_test.go @@ -2,6 +2,7 @@ package notifications import ( "context" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core/mocks" "testing" "github.com/stretchr/testify/assert" @@ -38,7 +39,7 @@ func TestGetEmailer(t *testing.T) { }, } - GetEmailer(cfg, promutils.NewTestScope()) + GetEmailer(cfg, promutils.NewTestScope(), &mocks.SecretManager{}) // shouldn't reach here t.Errorf("did not panic") @@ -47,7 +48,7 @@ func TestGetEmailer(t *testing.T) { func TestNewNotificationPublisherAndProcessor(t *testing.T) { testSandboxPublisher := NewNotificationsPublisher(notificationsConfig, scope) assert.IsType(t, testSandboxPublisher, &implementations.SandboxPublisher{}) - testSandboxProcessor := NewNotificationsProcessor(notificationsConfig, scope) + testSandboxProcessor := NewNotificationsProcessor(notificationsConfig, scope, &mocks.SecretManager{}) assert.IsType(t, testSandboxProcessor, &implementations.SandboxProcessor{}) go func() { diff --git a/flyteadmin/pkg/async/notifications/implementations/emailers.go b/flyteadmin/pkg/async/notifications/implementations/emailers.go index e630b5a4ea..bcef65424f 100644 --- a/flyteadmin/pkg/async/notifications/implementations/emailers.go +++ b/flyteadmin/pkg/async/notifications/implementations/emailers.go @@ -4,4 +4,5 @@ type ExternalEmailer = string const ( Sendgrid ExternalEmailer = "sendgrid" + Smtp ExternalEmailer = "smtp" ) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go new file mode 100644 index 0000000000..2306c6ec0e --- /dev/null +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go @@ -0,0 +1,143 @@ +package implementations + +import ( + "crypto/tls" + "fmt" + "github.com/flyteorg/flyte/flyteadmin/pkg/async/notifications/interfaces" + "github.com/flyteorg/flyte/flyteadmin/pkg/errors" + runtimeInterfaces "github.com/flyteorg/flyte/flyteadmin/pkg/runtime/interfaces" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/admin" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" + "github.com/flyteorg/flyte/flytestdlib/logger" + "github.com/flyteorg/flyte/flytestdlib/promutils" + "golang.org/x/net/context" + "google.golang.org/grpc/codes" + "net/smtp" + "strings" +) + +type SmtpEmailer struct { + config *runtimeInterfaces.NotificationsEmailerConfig + systemMetrics emailMetrics + tlsConf *tls.Config + auth *smtp.Auth +} + +func (s *SmtpEmailer) SendEmail(ctx context.Context, email admin.EmailMessage) error { + + newClient, err := smtp.Dial(s.config.EmailerConfig.SmtpServer + ":" + s.config.EmailerConfig.SmtpPort) + + if err != nil { + return s.emailError(ctx, fmt.Sprintf("Error creating email client: %s", err)) + } + + defer newClient.Close() + + if err = newClient.Hello("localhost"); err != nil { + return s.emailError(ctx, fmt.Sprintf("Error initiating connection to SMTP server: %s", err)) + } + + if ok, _ := newClient.Extension("STARTTLS"); ok { + if err = newClient.StartTLS(s.tlsConf); err != nil { + return err + } + } + + if ok, _ := newClient.Extension("AUTH"); ok { + if err = newClient.Auth(*s.auth); err != nil { + return s.emailError(ctx, fmt.Sprintf("Error authenticating email client: %s", err)) + } + } + + if err = newClient.Mail(email.SenderEmail); err != nil { + return s.emailError(ctx, fmt.Sprintf("Error creating email instance: %s", err)) + } + + for _, recipient := range email.RecipientsEmail { + if err = newClient.Rcpt(recipient); err != nil { + logger.Errorf(ctx, "Error adding email recipient: %s", err) + } + } + + writer, err := newClient.Data() + + if err != nil { + return s.emailError(ctx, fmt.Sprintf("Error adding email recipient: %s", err)) + } + + mailBody, err := createMailBody(s.config.Sender, email) + + if err != nil { + return s.emailError(ctx, fmt.Sprintf("Error creating email body: %s", err)) + } + + _, err = writer.Write([]byte(mailBody)) + + if err != nil { + return s.emailError(ctx, fmt.Sprintf("Error writing mail body: %s", err)) + } + + err = writer.Close() + + if err != nil { + return s.emailError(ctx, fmt.Sprintf("Error closing mail body: %s", err)) + } + + err = newClient.Quit() + + if err != nil { + return s.emailError(ctx, fmt.Sprintf("Error quitting mail agent: %s", err)) + } + + s.systemMetrics.SendSuccess.Inc() + return nil +} + +func (s *SmtpEmailer) emailError(ctx context.Context, error string) error { + s.systemMetrics.SendError.Inc() + logger.Error(ctx, error) + return errors.NewFlyteAdminErrorf(codes.Internal, "errors were seen while sending emails") +} + +func createMailBody(emailSender string, email admin.EmailMessage) (string, error) { + headerMap := make(map[string]string) + headerMap["From"] = emailSender + headerMap["To"] = strings.Join(email.RecipientsEmail, ",") + headerMap["Subject"] = email.SubjectLine + headerMap["Content-Type"] = "text/html; charset=\"UTF-8\"" + + mailMessage := "" + + for k, v := range headerMap { + mailMessage += fmt.Sprintf("%s: %s\r\n", k, v) + } + + mailMessage += "\r\n" + email.Body + + return mailMessage, nil +} + +func NewSmtpEmailer(ctx context.Context, config runtimeInterfaces.NotificationsConfig, scope promutils.Scope, sm core.SecretManager) interfaces.Emailer { + var tlsConfiguration *tls.Config + emailConf := config.NotificationsEmailerConfig.EmailerConfig + + smtpPassword, err := sm.Get(ctx, emailConf.SmtpPasswordSecretName) + if err != nil { + logger.Debug(ctx, "No SMTP password found.") + smtpPassword = "" + } + + auth := smtp.PlainAuth("", emailConf.SmtpUsername, smtpPassword, emailConf.SmtpServer) + + tlsConfiguration = &tls.Config{ + InsecureSkipVerify: emailConf.SmtpSkipTLSVerify, + ServerName: emailConf.SmtpServer, + } + + return &SmtpEmailer{ + config: &config.NotificationsEmailerConfig, + systemMetrics: newEmailMetrics(scope.NewSubScope("smtp")), + tlsConf: tlsConfiguration, + auth: &auth, + } +} diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go new file mode 100644 index 0000000000..2896cf0e4a --- /dev/null +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go @@ -0,0 +1,60 @@ +package implementations + +import ( + "context" + "github.com/flyteorg/flyte/flyteadmin/pkg/runtime/interfaces" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/admin" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core/mocks" + "github.com/flyteorg/flyte/flytestdlib/promutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "testing" +) + +func getNotificationsEmailerConfig() interfaces.NotificationsConfig { + return interfaces.NotificationsConfig{ + Type: "", + Region: "", + AWSConfig: interfaces.AWSConfig{}, + GCPConfig: interfaces.GCPConfig{}, + NotificationsPublisherConfig: interfaces.NotificationsPublisherConfig{}, + NotificationsProcessorConfig: interfaces.NotificationsProcessorConfig{}, + NotificationsEmailerConfig: interfaces.NotificationsEmailerConfig{ + EmailerConfig: interfaces.EmailServerConfig{ + ServiceName: Smtp, + SmtpServer: "smtpServer", + SmtpPort: "smtpPort", + SmtpUsername: "smtpUsername", + SmtpPasswordSecretName: "smtp_password", + }, + Subject: "subject", + Sender: "sender", + Body: "body"}, + ReconnectAttempts: 1, + ReconnectDelaySeconds: 2} +} + +func TestEmailCreation(t *testing.T) { + email := admin.EmailMessage{ + RecipientsEmail: []string{"john@doe.com", "teresa@tester.com"}, + SubjectLine: "subject", + Body: "Email Body", + SenderEmail: "sender@sender.com", + } + + body, err := createMailBody("sender@sender.com", email) + + assert.NoError(t, err) + assert.Equal(t, "From: sender@sender.com\r\nTo: john@doe.com,teresa@tester.com\r\nSubject: subject\r\nContent-Type: text/html; charset=\"UTF-8\"\r\n\r\nEmail Body", body) +} + +func TestNewSmtpEmailer(t *testing.T) { + secretManagerMock := mocks.SecretManager{} + secretManagerMock.On("Get", mock.Anything, "smtp_password").Return("password", nil) + + notificationsConfig := getNotificationsEmailerConfig() + + smtpEmailer := NewSmtpEmailer(context.Background(), notificationsConfig, promutils.NewTestScope(), &secretManagerMock) + + assert.NotNil(t, smtpEmailer) +} diff --git a/flyteadmin/pkg/rpc/adminservice/base.go b/flyteadmin/pkg/rpc/adminservice/base.go index 5a2cb2ad89..1463f9b39c 100644 --- a/flyteadmin/pkg/rpc/adminservice/base.go +++ b/flyteadmin/pkg/rpc/adminservice/base.go @@ -3,6 +3,7 @@ package adminservice import ( "context" "fmt" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" "runtime/debug" "github.com/golang/protobuf/proto" @@ -58,7 +59,7 @@ func (m *AdminService) interceptPanic(ctx context.Context, request proto.Message const defaultRetries = 3 func NewAdminServer(ctx context.Context, pluginRegistry *plugins.Registry, configuration runtimeIfaces.Configuration, - kubeConfig, master string, dataStorageClient *storage.DataStore, adminScope promutils.Scope) *AdminService { + kubeConfig, master string, dataStorageClient *storage.DataStore, adminScope promutils.Scope, sm core.SecretManager) *AdminService { applicationConfiguration := configuration.ApplicationConfiguration().GetTopLevelConfig() panicCounter := adminScope.MustNewCounter("initialization_panic", @@ -94,7 +95,7 @@ func NewAdminServer(ctx context.Context, pluginRegistry *plugins.Registry, confi pluginRegistry.RegisterDefault(plugins.PluginIDWorkflowExecutor, workflowExecutor) publisher := notifications.NewNotificationsPublisher(*configuration.ApplicationConfiguration().GetNotificationsConfig(), adminScope) - processor := notifications.NewNotificationsProcessor(*configuration.ApplicationConfiguration().GetNotificationsConfig(), adminScope) + processor := notifications.NewNotificationsProcessor(*configuration.ApplicationConfiguration().GetNotificationsConfig(), adminScope, sm) eventPublisher := notifications.NewEventsPublisher(*configuration.ApplicationConfiguration().GetExternalEventsConfig(), adminScope) go func() { logger.Info(ctx, "Started processing notifications.") diff --git a/flyteadmin/pkg/runtime/interfaces/application_configuration.go b/flyteadmin/pkg/runtime/interfaces/application_configuration.go index 092aa665b6..c0dc6ff25e 100644 --- a/flyteadmin/pkg/runtime/interfaces/application_configuration.go +++ b/flyteadmin/pkg/runtime/interfaces/application_configuration.go @@ -488,8 +488,13 @@ type NotificationsProcessorConfig struct { type EmailServerConfig struct { ServiceName string `json:"serviceName"` // Only one of these should be set. - APIKeyEnvVar string `json:"apiKeyEnvVar"` - APIKeyFilePath string `json:"apiKeyFilePath"` + APIKeyEnvVar string `json:"apiKeyEnvVar"` + APIKeyFilePath string `json:"apiKeyFilePath"` + SmtpServer string `json:"smtpServer"` + SmtpPort string `json:"smtpPort"` + SmtpSkipTLSVerify bool `json:"smtpSkipTLSVerify"` + SmtpUsername string `json:"smtpUsername"` + SmtpPasswordSecretName string `json:"smtpPasswordSecretName"` } // This section handles the configuration of notifications emails. diff --git a/flyteadmin/pkg/server/service.go b/flyteadmin/pkg/server/service.go index ff80c343d3..a215aba17e 100644 --- a/flyteadmin/pkg/server/service.go +++ b/flyteadmin/pkg/server/service.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "fmt" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" "net" "net/http" "strings" @@ -80,7 +81,7 @@ func SetMetricKeys(appConfig *runtimeIfaces.ApplicationConfig) { // Creates a new gRPC Server with all the configuration func newGRPCServer(ctx context.Context, pluginRegistry *plugins.Registry, cfg *config.ServerConfig, storageCfg *storage.Config, authCtx interfaces.AuthenticationContext, - scope promutils.Scope, opts ...grpc.ServerOption) (*grpc.Server, error) { + scope promutils.Scope, sm core.SecretManager, opts ...grpc.ServerOption) (*grpc.Server, error) { logger.Infof(ctx, "Registering default middleware with blanket auth validation") pluginRegistry.RegisterDefault(plugins.PluginIDUnaryServiceMiddleware, grpcmiddleware.ChainUnaryServer( @@ -131,7 +132,7 @@ func newGRPCServer(ctx context.Context, pluginRegistry *plugins.Registry, cfg *c } configuration := runtime2.NewConfigurationProvider() - adminServer := adminservice.NewAdminServer(ctx, pluginRegistry, configuration, cfg.KubeConfig, cfg.Master, dataStorageClient, scope.NewSubScope("admin")) + adminServer := adminservice.NewAdminServer(ctx, pluginRegistry, configuration, cfg.KubeConfig, cfg.Master, dataStorageClient, scope.NewSubScope("admin"), sm) grpcService.RegisterAdminServiceServer(grpcServer, adminServer) if cfg.Security.UseAuth { grpcService.RegisterAuthMetadataServiceServer(grpcServer, authCtx.AuthMetadataService()) @@ -315,12 +316,15 @@ func serveGatewayInsecure(ctx context.Context, pluginRegistry *plugins.Registry, // This will parse configuration and create the necessary objects for dealing with auth var authCtx interfaces.AuthenticationContext var err error + + sm := secretmanager.NewFileEnvSecretManager(secretmanager.GetConfig()) + // This code is here to support authentication without SSL. This setup supports a network topology where // Envoy does the SSL termination. The final hop is made over localhost only on a trusted machine. // Warning: Running authentication without SSL in any other topology is a severe security flaw. // See the auth.Config object for additional settings as well. if cfg.Security.UseAuth { - sm := secretmanager.NewFileEnvSecretManager(secretmanager.GetConfig()) + var oauth2Provider interfaces.OAuth2Provider var oauth2ResourceServer interfaces.OAuth2ResourceServer if authCfg.AppAuth.AuthServerType == authConfig.AuthorizationServerTypeSelf { @@ -349,7 +353,7 @@ func serveGatewayInsecure(ctx context.Context, pluginRegistry *plugins.Registry, } } - grpcServer, err := newGRPCServer(ctx, pluginRegistry, cfg, storageConfig, authCtx, scope) + grpcServer, err := newGRPCServer(ctx, pluginRegistry, cfg, storageConfig, authCtx, scope, sm) if err != nil { return fmt.Errorf("failed to create a newGRPCServer. Error: %w", err) } @@ -424,13 +428,14 @@ func serveGatewaySecure(ctx context.Context, pluginRegistry *plugins.Registry, c additionalHandlers map[string]func(http.ResponseWriter, *http.Request), scope promutils.Scope) error { certPool, cert, err := GetSslCredentials(ctx, cfg.Security.Ssl.CertificateFile, cfg.Security.Ssl.KeyFile) + sm := secretmanager.NewFileEnvSecretManager(secretmanager.GetConfig()) + if err != nil { return err } // This will parse configuration and create the necessary objects for dealing with auth var authCtx interfaces.AuthenticationContext if cfg.Security.UseAuth { - sm := secretmanager.NewFileEnvSecretManager(secretmanager.GetConfig()) var oauth2Provider interfaces.OAuth2Provider var oauth2ResourceServer interfaces.OAuth2ResourceServer if authCfg.AppAuth.AuthServerType == authConfig.AuthorizationServerTypeSelf { @@ -459,7 +464,7 @@ func serveGatewaySecure(ctx context.Context, pluginRegistry *plugins.Registry, c } } - grpcServer, err := newGRPCServer(ctx, pluginRegistry, cfg, storageCfg, authCtx, scope, grpc.Creds(credentials.NewServerTLSFromCert(cert))) + grpcServer, err := newGRPCServer(ctx, pluginRegistry, cfg, storageCfg, authCtx, scope, sm, grpc.Creds(credentials.NewServerTLSFromCert(cert))) if err != nil { return fmt.Errorf("failed to create a newGRPCServer. Error: %w", err) } From 3296a1a3fb4fa14efba2c0a039b791e5915c9d70 Mon Sep 17 00:00:00 2001 From: Rob Ulbrich Date: Wed, 3 Jul 2024 14:28:32 +0200 Subject: [PATCH 02/28] Fixing lint issues Signed-off-by: Rob Ulbrich --- flyteadmin/pkg/async/notifications/factory.go | 4 +-- .../notifications/implementations/emailers.go | 2 +- .../implementations/smtp_emailer.go | 32 ++++++++----------- .../implementations/smtp_emailer_test.go | 4 +-- .../interfaces/application_configuration.go | 10 +++--- 5 files changed, 23 insertions(+), 29 deletions(-) diff --git a/flyteadmin/pkg/async/notifications/factory.go b/flyteadmin/pkg/async/notifications/factory.go index 1a49340908..3468ae586f 100644 --- a/flyteadmin/pkg/async/notifications/factory.go +++ b/flyteadmin/pkg/async/notifications/factory.go @@ -58,8 +58,8 @@ func GetEmailer(config runtimeInterfaces.NotificationsConfig, scope promutils.Sc switch config.NotificationsEmailerConfig.EmailerConfig.ServiceName { case implementations.Sendgrid: return implementations.NewSendGridEmailer(config, scope) - case implementations.Smtp: - return implementations.NewSmtpEmailer(context.Background(), config, scope, sm) + case implementations.SMTP: + return implementations.NewSMTPEmailer(context.Background(), config, scope, sm) default: panic(fmt.Errorf("No matching email implementation for %s", config.NotificationsEmailerConfig.EmailerConfig.ServiceName)) } diff --git a/flyteadmin/pkg/async/notifications/implementations/emailers.go b/flyteadmin/pkg/async/notifications/implementations/emailers.go index bcef65424f..0da3fbf600 100644 --- a/flyteadmin/pkg/async/notifications/implementations/emailers.go +++ b/flyteadmin/pkg/async/notifications/implementations/emailers.go @@ -4,5 +4,5 @@ type ExternalEmailer = string const ( Sendgrid ExternalEmailer = "sendgrid" - Smtp ExternalEmailer = "smtp" + SMTP ExternalEmailer = "smtp" ) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go index 2306c6ec0e..90190d8a03 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go @@ -16,16 +16,16 @@ import ( "strings" ) -type SmtpEmailer struct { +type SMTPEmailer struct { config *runtimeInterfaces.NotificationsEmailerConfig systemMetrics emailMetrics tlsConf *tls.Config auth *smtp.Auth } -func (s *SmtpEmailer) SendEmail(ctx context.Context, email admin.EmailMessage) error { +func (s *SMTPEmailer) SendEmail(ctx context.Context, email admin.EmailMessage) error { - newClient, err := smtp.Dial(s.config.EmailerConfig.SmtpServer + ":" + s.config.EmailerConfig.SmtpPort) + newClient, err := smtp.Dial(s.config.EmailerConfig.SMTPServer + ":" + s.config.EmailerConfig.SMTPPort) if err != nil { return s.emailError(ctx, fmt.Sprintf("Error creating email client: %s", err)) @@ -65,13 +65,7 @@ func (s *SmtpEmailer) SendEmail(ctx context.Context, email admin.EmailMessage) e return s.emailError(ctx, fmt.Sprintf("Error adding email recipient: %s", err)) } - mailBody, err := createMailBody(s.config.Sender, email) - - if err != nil { - return s.emailError(ctx, fmt.Sprintf("Error creating email body: %s", err)) - } - - _, err = writer.Write([]byte(mailBody)) + _, err = writer.Write([]byte(createMailBody(s.config.Sender, email))) if err != nil { return s.emailError(ctx, fmt.Sprintf("Error writing mail body: %s", err)) @@ -93,13 +87,13 @@ func (s *SmtpEmailer) SendEmail(ctx context.Context, email admin.EmailMessage) e return nil } -func (s *SmtpEmailer) emailError(ctx context.Context, error string) error { +func (s *SMTPEmailer) emailError(ctx context.Context, error string) error { s.systemMetrics.SendError.Inc() logger.Error(ctx, error) return errors.NewFlyteAdminErrorf(codes.Internal, "errors were seen while sending emails") } -func createMailBody(emailSender string, email admin.EmailMessage) (string, error) { +func createMailBody(emailSender string, email admin.EmailMessage) string { headerMap := make(map[string]string) headerMap["From"] = emailSender headerMap["To"] = strings.Join(email.RecipientsEmail, ",") @@ -114,27 +108,27 @@ func createMailBody(emailSender string, email admin.EmailMessage) (string, error mailMessage += "\r\n" + email.Body - return mailMessage, nil + return mailMessage } -func NewSmtpEmailer(ctx context.Context, config runtimeInterfaces.NotificationsConfig, scope promutils.Scope, sm core.SecretManager) interfaces.Emailer { +func NewSMTPEmailer(ctx context.Context, config runtimeInterfaces.NotificationsConfig, scope promutils.Scope, sm core.SecretManager) interfaces.Emailer { var tlsConfiguration *tls.Config emailConf := config.NotificationsEmailerConfig.EmailerConfig - smtpPassword, err := sm.Get(ctx, emailConf.SmtpPasswordSecretName) + smtpPassword, err := sm.Get(ctx, emailConf.SMTPPasswordSecretName) if err != nil { logger.Debug(ctx, "No SMTP password found.") smtpPassword = "" } - auth := smtp.PlainAuth("", emailConf.SmtpUsername, smtpPassword, emailConf.SmtpServer) + auth := smtp.PlainAuth("", emailConf.SMTPUsername, smtpPassword, emailConf.SMTPServer) tlsConfiguration = &tls.Config{ - InsecureSkipVerify: emailConf.SmtpSkipTLSVerify, - ServerName: emailConf.SmtpServer, + InsecureSkipVerify: emailConf.SMTPSkipTLSVerify, + ServerName: emailConf.SMTPServer, } - return &SmtpEmailer{ + return &SMTPEmailer{ config: &config.NotificationsEmailerConfig, systemMetrics: newEmailMetrics(scope.NewSubScope("smtp")), tlsConf: tlsConfiguration, diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go index 2896cf0e4a..fc3593db10 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go @@ -21,7 +21,7 @@ func getNotificationsEmailerConfig() interfaces.NotificationsConfig { NotificationsProcessorConfig: interfaces.NotificationsProcessorConfig{}, NotificationsEmailerConfig: interfaces.NotificationsEmailerConfig{ EmailerConfig: interfaces.EmailServerConfig{ - ServiceName: Smtp, + ServiceName: SMTP, SmtpServer: "smtpServer", SmtpPort: "smtpPort", SmtpUsername: "smtpUsername", @@ -54,7 +54,7 @@ func TestNewSmtpEmailer(t *testing.T) { notificationsConfig := getNotificationsEmailerConfig() - smtpEmailer := NewSmtpEmailer(context.Background(), notificationsConfig, promutils.NewTestScope(), &secretManagerMock) + smtpEmailer := NewSMTPEmailer(context.Background(), notificationsConfig, promutils.NewTestScope(), &secretManagerMock) assert.NotNil(t, smtpEmailer) } diff --git a/flyteadmin/pkg/runtime/interfaces/application_configuration.go b/flyteadmin/pkg/runtime/interfaces/application_configuration.go index c0dc6ff25e..318ce1a54c 100644 --- a/flyteadmin/pkg/runtime/interfaces/application_configuration.go +++ b/flyteadmin/pkg/runtime/interfaces/application_configuration.go @@ -490,11 +490,11 @@ type EmailServerConfig struct { // Only one of these should be set. APIKeyEnvVar string `json:"apiKeyEnvVar"` APIKeyFilePath string `json:"apiKeyFilePath"` - SmtpServer string `json:"smtpServer"` - SmtpPort string `json:"smtpPort"` - SmtpSkipTLSVerify bool `json:"smtpSkipTLSVerify"` - SmtpUsername string `json:"smtpUsername"` - SmtpPasswordSecretName string `json:"smtpPasswordSecretName"` + SMTPServer string `json:"smtpServer"` + SMTPPort string `json:"smtpPort"` + SMTPSkipTLSVerify bool `json:"smtpSkipTLSVerify"` + SMTPUsername string `json:"smtpUsername"` + SMTPPasswordSecretName string `json:"smtpPasswordSecretName"` } // This section handles the configuration of notifications emails. From d6bc1a4b00801387b621bac9796ccf430e852d40 Mon Sep 17 00:00:00 2001 From: Rob Ulbrich Date: Thu, 4 Jul 2024 09:45:40 +0200 Subject: [PATCH 03/28] Fixing tests Signed-off-by: Rob Ulbrich --- .../implementations/smtp_emailer_test.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go index fc3593db10..ed56ad2e25 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go @@ -22,10 +22,10 @@ func getNotificationsEmailerConfig() interfaces.NotificationsConfig { NotificationsEmailerConfig: interfaces.NotificationsEmailerConfig{ EmailerConfig: interfaces.EmailServerConfig{ ServiceName: SMTP, - SmtpServer: "smtpServer", - SmtpPort: "smtpPort", - SmtpUsername: "smtpUsername", - SmtpPasswordSecretName: "smtp_password", + SMTPServer: "smtpServer", + SMTPPort: "smtpPort", + SMTPUsername: "smtpUsername", + SMTPPasswordSecretName: "smtp_password", }, Subject: "subject", Sender: "sender", @@ -42,9 +42,7 @@ func TestEmailCreation(t *testing.T) { SenderEmail: "sender@sender.com", } - body, err := createMailBody("sender@sender.com", email) - - assert.NoError(t, err) + body := createMailBody("sender@sender.com", email) assert.Equal(t, "From: sender@sender.com\r\nTo: john@doe.com,teresa@tester.com\r\nSubject: subject\r\nContent-Type: text/html; charset=\"UTF-8\"\r\n\r\nEmail Body", body) } From a740c8e0fb7b231122fd3020fd69ace641c5527c Mon Sep 17 00:00:00 2001 From: Rob Ulbrich Date: Thu, 4 Jul 2024 13:54:20 +0200 Subject: [PATCH 04/28] Fixing lint Signed-off-by: Rob Ulbrich --- .../pkg/async/notifications/implementations/smtp_emailer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go index 90190d8a03..3e68651e6e 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go @@ -123,7 +123,7 @@ func NewSMTPEmailer(ctx context.Context, config runtimeInterfaces.NotificationsC auth := smtp.PlainAuth("", emailConf.SMTPUsername, smtpPassword, emailConf.SMTPServer) - tlsConfiguration = &tls.Config{ + tlsConfiguration = &tls.Config{ // #nosec G402 InsecureSkipVerify: emailConf.SMTPSkipTLSVerify, ServerName: emailConf.SMTPServer, } From 4be6a0e3aa22f7a63606cc4953d283c0a2035cca Mon Sep 17 00:00:00 2001 From: Rob Ulbrich Date: Fri, 5 Jul 2024 06:47:56 +0200 Subject: [PATCH 05/28] Fixing lint Signed-off-by: Rob Ulbrich --- .../pkg/async/notifications/implementations/smtp_emailer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go index 3e68651e6e..0eb67a41db 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go @@ -123,8 +123,8 @@ func NewSMTPEmailer(ctx context.Context, config runtimeInterfaces.NotificationsC auth := smtp.PlainAuth("", emailConf.SMTPUsername, smtpPassword, emailConf.SMTPServer) - tlsConfiguration = &tls.Config{ // #nosec G402 - InsecureSkipVerify: emailConf.SMTPSkipTLSVerify, + tlsConfiguration = &tls.Config{ + InsecureSkipVerify: emailConf.SMTPSkipTLSVerify, // #nosec G402 ServerName: emailConf.SMTPServer, } From 4353452ad2e707159ceb1b58dee89e024c196860 Mon Sep 17 00:00:00 2001 From: Rob Ulbrich Date: Mon, 15 Jul 2024 14:26:21 +0200 Subject: [PATCH 06/28] Fix linting issues Signed-off-by: Rob Ulbrich --- flyteadmin/pkg/async/notifications/factory.go | 3 ++- flyteadmin/pkg/async/notifications/factory_test.go | 5 ++--- .../pkg/async/notifications/implementations/smtp_emailer.go | 5 +++-- .../async/notifications/implementations/smtp_emailer_test.go | 3 ++- flyteadmin/pkg/rpc/adminservice/base.go | 5 ++--- flyteadmin/pkg/server/service.go | 3 ++- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/flyteadmin/pkg/async/notifications/factory.go b/flyteadmin/pkg/async/notifications/factory.go index 3468ae586f..5698c3597e 100644 --- a/flyteadmin/pkg/async/notifications/factory.go +++ b/flyteadmin/pkg/async/notifications/factory.go @@ -3,10 +3,11 @@ package notifications import ( "context" "fmt" - "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" "sync" "time" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" + "github.com/NYTimes/gizmo/pubsub" gizmoAWS "github.com/NYTimes/gizmo/pubsub/aws" gizmoGCP "github.com/NYTimes/gizmo/pubsub/gcp" diff --git a/flyteadmin/pkg/async/notifications/factory_test.go b/flyteadmin/pkg/async/notifications/factory_test.go index 3505705766..72cdb88794 100644 --- a/flyteadmin/pkg/async/notifications/factory_test.go +++ b/flyteadmin/pkg/async/notifications/factory_test.go @@ -2,15 +2,14 @@ package notifications import ( "context" - "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core/mocks" "testing" - "github.com/stretchr/testify/assert" - "github.com/flyteorg/flyte/flyteadmin/pkg/async/notifications/implementations" runtimeInterfaces "github.com/flyteorg/flyte/flyteadmin/pkg/runtime/interfaces" "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/admin" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core/mocks" "github.com/flyteorg/flyte/flytestdlib/promutils" + "github.com/stretchr/testify/assert" ) var ( diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go index 0eb67a41db..7c079d2d77 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go @@ -3,6 +3,9 @@ package implementations import ( "crypto/tls" "fmt" + "net/smtp" + "strings" + "github.com/flyteorg/flyte/flyteadmin/pkg/async/notifications/interfaces" "github.com/flyteorg/flyte/flyteadmin/pkg/errors" runtimeInterfaces "github.com/flyteorg/flyte/flyteadmin/pkg/runtime/interfaces" @@ -12,8 +15,6 @@ import ( "github.com/flyteorg/flyte/flytestdlib/promutils" "golang.org/x/net/context" "google.golang.org/grpc/codes" - "net/smtp" - "strings" ) type SMTPEmailer struct { diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go index ed56ad2e25..be664331f9 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go @@ -2,13 +2,14 @@ package implementations import ( "context" + "testing" + "github.com/flyteorg/flyte/flyteadmin/pkg/runtime/interfaces" "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/admin" "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core/mocks" "github.com/flyteorg/flyte/flytestdlib/promutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "testing" ) func getNotificationsEmailerConfig() interfaces.NotificationsConfig { diff --git a/flyteadmin/pkg/rpc/adminservice/base.go b/flyteadmin/pkg/rpc/adminservice/base.go index 1463f9b39c..50be38d44c 100644 --- a/flyteadmin/pkg/rpc/adminservice/base.go +++ b/flyteadmin/pkg/rpc/adminservice/base.go @@ -3,11 +3,8 @@ package adminservice import ( "context" "fmt" - "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" "runtime/debug" - "github.com/golang/protobuf/proto" - "github.com/flyteorg/flyte/flyteadmin/pkg/async/cloudevent" eventWriter "github.com/flyteorg/flyte/flyteadmin/pkg/async/events/implementations" "github.com/flyteorg/flyte/flyteadmin/pkg/async/notifications" @@ -23,9 +20,11 @@ import ( workflowengineImpl "github.com/flyteorg/flyte/flyteadmin/pkg/workflowengine/impl" "github.com/flyteorg/flyte/flyteadmin/plugins" "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/service" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" "github.com/flyteorg/flyte/flytestdlib/logger" "github.com/flyteorg/flyte/flytestdlib/promutils" "github.com/flyteorg/flyte/flytestdlib/storage" + "github.com/golang/protobuf/proto" ) type AdminService struct { diff --git a/flyteadmin/pkg/server/service.go b/flyteadmin/pkg/server/service.go index a215aba17e..8ce931e9bd 100644 --- a/flyteadmin/pkg/server/service.go +++ b/flyteadmin/pkg/server/service.go @@ -4,12 +4,13 @@ import ( "context" "crypto/tls" "fmt" - "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" "net" "net/http" "strings" "time" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" + "github.com/gorilla/handlers" grpcmiddleware "github.com/grpc-ecosystem/go-grpc-middleware" grpcauth "github.com/grpc-ecosystem/go-grpc-middleware/auth" From cada9d8be547a062082dbe4e8bbe992aa923efba Mon Sep 17 00:00:00 2001 From: Ulbrich Robert Date: Fri, 16 Aug 2024 10:40:27 +0200 Subject: [PATCH 07/28] Fix go mod Signed-off-by: Ulbrich Robert --- flyteadmin/go.mod | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/flyteadmin/go.mod b/flyteadmin/go.mod index deaee43177..b99630da3e 100644 --- a/flyteadmin/go.mod +++ b/flyteadmin/go.mod @@ -50,7 +50,7 @@ require ( github.com/wI2L/jsondiff v0.5.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 go.opentelemetry.io/otel v1.24.0 - golang.org/x/net v0.23.0 + golang.org/x/net v0.27.0 golang.org/x/oauth2 v0.16.0 golang.org/x/time v0.5.0 google.golang.org/api v0.155.0 @@ -189,7 +189,6 @@ require ( go.opentelemetry.io/proto/otlp v1.1.0 // indirect golang.org/x/crypto v0.25.0 // indirect golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect - golang.org/x/net v0.27.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/term v0.22.0 // indirect From 73bb3056b04c79a75a27e215c84cf2318b07843d Mon Sep 17 00:00:00 2001 From: Ulbrich Robert Date: Thu, 5 Sep 2024 11:22:48 +0200 Subject: [PATCH 08/28] Fixing location of no sec comment Signed-off-by: Ulbrich Robert --- .../pkg/async/notifications/implementations/smtp_emailer.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go index 7c079d2d77..dce1adfd00 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go @@ -124,8 +124,9 @@ func NewSMTPEmailer(ctx context.Context, config runtimeInterfaces.NotificationsC auth := smtp.PlainAuth("", emailConf.SMTPUsername, smtpPassword, emailConf.SMTPServer) + // #nosec G402 tlsConfiguration = &tls.Config{ - InsecureSkipVerify: emailConf.SMTPSkipTLSVerify, // #nosec G402 + InsecureSkipVerify: emailConf.SMTPSkipTLSVerify, ServerName: emailConf.SMTPServer, } From c7901bcd54661595cd5ce02005c18778c272f2f9 Mon Sep 17 00:00:00 2001 From: Ulbrich Robert Date: Thu, 5 Sep 2024 12:30:38 +0200 Subject: [PATCH 09/28] Removing unused import Signed-off-by: Ulbrich Robert --- flyteadmin/pkg/rpc/adminservice/base.go | 1 - 1 file changed, 1 deletion(-) diff --git a/flyteadmin/pkg/rpc/adminservice/base.go b/flyteadmin/pkg/rpc/adminservice/base.go index b638b8e3cb..491a24a1f0 100644 --- a/flyteadmin/pkg/rpc/adminservice/base.go +++ b/flyteadmin/pkg/rpc/adminservice/base.go @@ -24,7 +24,6 @@ import ( "github.com/flyteorg/flyte/flytestdlib/logger" "github.com/flyteorg/flyte/flytestdlib/promutils" "github.com/flyteorg/flyte/flytestdlib/storage" - "github.com/golang/protobuf/proto" ) type AdminService struct { From b2653046025b49adcccaee74ad450344bbf46c3e Mon Sep 17 00:00:00 2001 From: Ulbrich Robert Date: Mon, 9 Sep 2024 15:19:35 +0200 Subject: [PATCH 10/28] Running gci for file Signed-off-by: Ulbrich Robert --- .../async/notifications/implementations/smtp_emailer_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go index be664331f9..320a3c6ea4 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go @@ -4,12 +4,13 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/flyteorg/flyte/flyteadmin/pkg/runtime/interfaces" "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/admin" "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core/mocks" "github.com/flyteorg/flyte/flytestdlib/promutils" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" ) func getNotificationsEmailerConfig() interfaces.NotificationsConfig { From eda4d3e1a095c73f2a3d039a151b12449db8508e Mon Sep 17 00:00:00 2001 From: Ulbrich Robert Date: Tue, 10 Sep 2024 14:26:58 +0200 Subject: [PATCH 11/28] Implemented connection reuse for smtp emailer Signed-off-by: Ulbrich Robert --- .../implementations/smtp_emailer.go | 53 ++++++++++++------- flyteadmin/pkg/server/service.go | 3 +- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go index dce1adfd00..94fa82577f 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go @@ -6,6 +6,9 @@ import ( "net/smtp" "strings" + "golang.org/x/net/context" + "google.golang.org/grpc/codes" + "github.com/flyteorg/flyte/flyteadmin/pkg/async/notifications/interfaces" "github.com/flyteorg/flyte/flyteadmin/pkg/errors" runtimeInterfaces "github.com/flyteorg/flyte/flyteadmin/pkg/runtime/interfaces" @@ -13,8 +16,6 @@ import ( "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" "github.com/flyteorg/flyte/flytestdlib/logger" "github.com/flyteorg/flyte/flytestdlib/promutils" - "golang.org/x/net/context" - "google.golang.org/grpc/codes" ) type SMTPEmailer struct { @@ -22,45 +23,65 @@ type SMTPEmailer struct { systemMetrics emailMetrics tlsConf *tls.Config auth *smtp.Auth + smtpClient *smtp.Client } -func (s *SMTPEmailer) SendEmail(ctx context.Context, email admin.EmailMessage) error { - +func (s *SMTPEmailer) createClient(ctx context.Context) (*smtp.Client, error) { newClient, err := smtp.Dial(s.config.EmailerConfig.SMTPServer + ":" + s.config.EmailerConfig.SMTPPort) if err != nil { - return s.emailError(ctx, fmt.Sprintf("Error creating email client: %s", err)) + return nil, s.emailError(ctx, fmt.Sprintf("Error creating email client: %s", err)) } - defer newClient.Close() - if err = newClient.Hello("localhost"); err != nil { - return s.emailError(ctx, fmt.Sprintf("Error initiating connection to SMTP server: %s", err)) + return nil, s.emailError(ctx, fmt.Sprintf("Error initiating connection to SMTP server: %s", err)) } if ok, _ := newClient.Extension("STARTTLS"); ok { if err = newClient.StartTLS(s.tlsConf); err != nil { - return err + return nil, err } } if ok, _ := newClient.Extension("AUTH"); ok { if err = newClient.Auth(*s.auth); err != nil { - return s.emailError(ctx, fmt.Sprintf("Error authenticating email client: %s", err)) + return nil, s.emailError(ctx, fmt.Sprintf("Error authenticating email client: %s", err)) } } - if err = newClient.Mail(email.SenderEmail); err != nil { + return newClient, nil +} + +func (s *SMTPEmailer) SendEmail(ctx context.Context, email admin.EmailMessage) error { + + if s.smtpClient == nil || s.smtpClient.Noop() != nil { + + if s.smtpClient != nil { + err := s.smtpClient.Close() + if err != nil { + logger.Info(ctx, err) + } + } + smtpClient, err := s.createClient(ctx) + + if err != nil { + return s.emailError(ctx, fmt.Sprintf("Error creating SMTP email client: %s", err)) + } + + s.smtpClient = smtpClient + } + + if err := s.smtpClient.Mail(email.SenderEmail); err != nil { return s.emailError(ctx, fmt.Sprintf("Error creating email instance: %s", err)) } for _, recipient := range email.RecipientsEmail { - if err = newClient.Rcpt(recipient); err != nil { + if err := s.smtpClient.Rcpt(recipient); err != nil { logger.Errorf(ctx, "Error adding email recipient: %s", err) } } - writer, err := newClient.Data() + writer, err := s.smtpClient.Data() if err != nil { return s.emailError(ctx, fmt.Sprintf("Error adding email recipient: %s", err)) @@ -78,12 +99,6 @@ func (s *SMTPEmailer) SendEmail(ctx context.Context, email admin.EmailMessage) e return s.emailError(ctx, fmt.Sprintf("Error closing mail body: %s", err)) } - err = newClient.Quit() - - if err != nil { - return s.emailError(ctx, fmt.Sprintf("Error quitting mail agent: %s", err)) - } - s.systemMetrics.SendSuccess.Inc() return nil } diff --git a/flyteadmin/pkg/server/service.go b/flyteadmin/pkg/server/service.go index 32b63c9273..840d0d9f17 100644 --- a/flyteadmin/pkg/server/service.go +++ b/flyteadmin/pkg/server/service.go @@ -9,8 +9,6 @@ import ( "strings" "time" - "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" - "github.com/gorilla/handlers" grpcmiddleware "github.com/grpc-ecosystem/go-grpc-middleware" grpcauth "github.com/grpc-ecosystem/go-grpc-middleware/auth" @@ -45,6 +43,7 @@ import ( "github.com/flyteorg/flyte/flyteidl/clients/go/assets" grpcService "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/service" "github.com/flyteorg/flyte/flyteidl/gen/pb-go/gateway/flyteidl/service" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" "github.com/flyteorg/flyte/flytepropeller/pkg/controller/nodes/task/secretmanager" "github.com/flyteorg/flyte/flytestdlib/contextutils" "github.com/flyteorg/flyte/flytestdlib/logger" From 445b9797c1c400f6892ff72c25d8256646cabd53 Mon Sep 17 00:00:00 2001 From: ULBRICR Date: Mon, 1 Jul 2024 09:39:21 +0200 Subject: [PATCH 12/28] Introduced SMTP notification Introduced SMTP notification for Flyte Signed-off-by: Rob Ulbrich --- flyteadmin/go.mod | 1 + flyteadmin/pkg/async/notifications/factory.go | 13 +- .../pkg/async/notifications/factory_test.go | 5 +- .../notifications/implementations/emailers.go | 1 + .../implementations/smtp_emailer.go | 143 ++++++++++++++++++ .../implementations/smtp_emailer_test.go | 60 ++++++++ flyteadmin/pkg/rpc/adminservice/base.go | 5 +- .../interfaces/application_configuration.go | 9 +- flyteadmin/pkg/server/service.go | 17 ++- 9 files changed, 237 insertions(+), 17 deletions(-) create mode 100644 flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go create mode 100644 flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go diff --git a/flyteadmin/go.mod b/flyteadmin/go.mod index b9eba5b83a..1ff9cd2464 100644 --- a/flyteadmin/go.mod +++ b/flyteadmin/go.mod @@ -51,6 +51,7 @@ require ( github.com/wolfeidau/humanhash v1.1.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 go.opentelemetry.io/otel v1.24.0 + golang.org/x/net v0.23.0 golang.org/x/oauth2 v0.16.0 golang.org/x/time v0.5.0 google.golang.org/api v0.155.0 diff --git a/flyteadmin/pkg/async/notifications/factory.go b/flyteadmin/pkg/async/notifications/factory.go index f94129a1d5..1a49340908 100644 --- a/flyteadmin/pkg/async/notifications/factory.go +++ b/flyteadmin/pkg/async/notifications/factory.go @@ -3,6 +3,7 @@ package notifications import ( "context" "fmt" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" "sync" "time" @@ -50,13 +51,15 @@ func CreateMsgChan() { }) } -func GetEmailer(config runtimeInterfaces.NotificationsConfig, scope promutils.Scope) interfaces.Emailer { +func GetEmailer(config runtimeInterfaces.NotificationsConfig, scope promutils.Scope, sm core.SecretManager) interfaces.Emailer { // If an external email service is specified use that instead. // TODO: Handling of this is messy, see https://github.com/flyteorg/flyte/issues/1063 if config.NotificationsEmailerConfig.EmailerConfig.ServiceName != "" { switch config.NotificationsEmailerConfig.EmailerConfig.ServiceName { case implementations.Sendgrid: return implementations.NewSendGridEmailer(config, scope) + case implementations.Smtp: + return implementations.NewSmtpEmailer(context.Background(), config, scope, sm) default: panic(fmt.Errorf("No matching email implementation for %s", config.NotificationsEmailerConfig.EmailerConfig.ServiceName)) } @@ -87,7 +90,7 @@ func GetEmailer(config runtimeInterfaces.NotificationsConfig, scope promutils.Sc } } -func NewNotificationsProcessor(config runtimeInterfaces.NotificationsConfig, scope promutils.Scope) interfaces.Processor { +func NewNotificationsProcessor(config runtimeInterfaces.NotificationsConfig, scope promutils.Scope, sm core.SecretManager) interfaces.Processor { reconnectAttempts := config.ReconnectAttempts reconnectDelay := time.Duration(config.ReconnectDelaySeconds) * time.Second var sub pubsub.Subscriber @@ -119,7 +122,7 @@ func NewNotificationsProcessor(config runtimeInterfaces.NotificationsConfig, sco if err != nil { panic(err) } - emailer = GetEmailer(config, scope) + emailer = GetEmailer(config, scope, sm) return implementations.NewProcessor(sub, emailer, scope) case common.GCP: projectID := config.GCPConfig.ProjectID @@ -135,10 +138,10 @@ func NewNotificationsProcessor(config runtimeInterfaces.NotificationsConfig, sco if err != nil { panic(err) } - emailer = GetEmailer(config, scope) + emailer = GetEmailer(config, scope, sm) return implementations.NewGcpProcessor(sub, emailer, scope) case common.Sandbox: - emailer = GetEmailer(config, scope) + emailer = GetEmailer(config, scope, sm) return implementations.NewSandboxProcessor(msgChan, emailer) case common.Local: fallthrough diff --git a/flyteadmin/pkg/async/notifications/factory_test.go b/flyteadmin/pkg/async/notifications/factory_test.go index 1bfd1f4596..3505705766 100644 --- a/flyteadmin/pkg/async/notifications/factory_test.go +++ b/flyteadmin/pkg/async/notifications/factory_test.go @@ -2,6 +2,7 @@ package notifications import ( "context" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core/mocks" "testing" "github.com/stretchr/testify/assert" @@ -38,7 +39,7 @@ func TestGetEmailer(t *testing.T) { }, } - GetEmailer(cfg, promutils.NewTestScope()) + GetEmailer(cfg, promutils.NewTestScope(), &mocks.SecretManager{}) // shouldn't reach here t.Errorf("did not panic") @@ -47,7 +48,7 @@ func TestGetEmailer(t *testing.T) { func TestNewNotificationPublisherAndProcessor(t *testing.T) { testSandboxPublisher := NewNotificationsPublisher(notificationsConfig, scope) assert.IsType(t, testSandboxPublisher, &implementations.SandboxPublisher{}) - testSandboxProcessor := NewNotificationsProcessor(notificationsConfig, scope) + testSandboxProcessor := NewNotificationsProcessor(notificationsConfig, scope, &mocks.SecretManager{}) assert.IsType(t, testSandboxProcessor, &implementations.SandboxProcessor{}) go func() { diff --git a/flyteadmin/pkg/async/notifications/implementations/emailers.go b/flyteadmin/pkg/async/notifications/implementations/emailers.go index e630b5a4ea..bcef65424f 100644 --- a/flyteadmin/pkg/async/notifications/implementations/emailers.go +++ b/flyteadmin/pkg/async/notifications/implementations/emailers.go @@ -4,4 +4,5 @@ type ExternalEmailer = string const ( Sendgrid ExternalEmailer = "sendgrid" + Smtp ExternalEmailer = "smtp" ) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go new file mode 100644 index 0000000000..2306c6ec0e --- /dev/null +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go @@ -0,0 +1,143 @@ +package implementations + +import ( + "crypto/tls" + "fmt" + "github.com/flyteorg/flyte/flyteadmin/pkg/async/notifications/interfaces" + "github.com/flyteorg/flyte/flyteadmin/pkg/errors" + runtimeInterfaces "github.com/flyteorg/flyte/flyteadmin/pkg/runtime/interfaces" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/admin" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" + "github.com/flyteorg/flyte/flytestdlib/logger" + "github.com/flyteorg/flyte/flytestdlib/promutils" + "golang.org/x/net/context" + "google.golang.org/grpc/codes" + "net/smtp" + "strings" +) + +type SmtpEmailer struct { + config *runtimeInterfaces.NotificationsEmailerConfig + systemMetrics emailMetrics + tlsConf *tls.Config + auth *smtp.Auth +} + +func (s *SmtpEmailer) SendEmail(ctx context.Context, email admin.EmailMessage) error { + + newClient, err := smtp.Dial(s.config.EmailerConfig.SmtpServer + ":" + s.config.EmailerConfig.SmtpPort) + + if err != nil { + return s.emailError(ctx, fmt.Sprintf("Error creating email client: %s", err)) + } + + defer newClient.Close() + + if err = newClient.Hello("localhost"); err != nil { + return s.emailError(ctx, fmt.Sprintf("Error initiating connection to SMTP server: %s", err)) + } + + if ok, _ := newClient.Extension("STARTTLS"); ok { + if err = newClient.StartTLS(s.tlsConf); err != nil { + return err + } + } + + if ok, _ := newClient.Extension("AUTH"); ok { + if err = newClient.Auth(*s.auth); err != nil { + return s.emailError(ctx, fmt.Sprintf("Error authenticating email client: %s", err)) + } + } + + if err = newClient.Mail(email.SenderEmail); err != nil { + return s.emailError(ctx, fmt.Sprintf("Error creating email instance: %s", err)) + } + + for _, recipient := range email.RecipientsEmail { + if err = newClient.Rcpt(recipient); err != nil { + logger.Errorf(ctx, "Error adding email recipient: %s", err) + } + } + + writer, err := newClient.Data() + + if err != nil { + return s.emailError(ctx, fmt.Sprintf("Error adding email recipient: %s", err)) + } + + mailBody, err := createMailBody(s.config.Sender, email) + + if err != nil { + return s.emailError(ctx, fmt.Sprintf("Error creating email body: %s", err)) + } + + _, err = writer.Write([]byte(mailBody)) + + if err != nil { + return s.emailError(ctx, fmt.Sprintf("Error writing mail body: %s", err)) + } + + err = writer.Close() + + if err != nil { + return s.emailError(ctx, fmt.Sprintf("Error closing mail body: %s", err)) + } + + err = newClient.Quit() + + if err != nil { + return s.emailError(ctx, fmt.Sprintf("Error quitting mail agent: %s", err)) + } + + s.systemMetrics.SendSuccess.Inc() + return nil +} + +func (s *SmtpEmailer) emailError(ctx context.Context, error string) error { + s.systemMetrics.SendError.Inc() + logger.Error(ctx, error) + return errors.NewFlyteAdminErrorf(codes.Internal, "errors were seen while sending emails") +} + +func createMailBody(emailSender string, email admin.EmailMessage) (string, error) { + headerMap := make(map[string]string) + headerMap["From"] = emailSender + headerMap["To"] = strings.Join(email.RecipientsEmail, ",") + headerMap["Subject"] = email.SubjectLine + headerMap["Content-Type"] = "text/html; charset=\"UTF-8\"" + + mailMessage := "" + + for k, v := range headerMap { + mailMessage += fmt.Sprintf("%s: %s\r\n", k, v) + } + + mailMessage += "\r\n" + email.Body + + return mailMessage, nil +} + +func NewSmtpEmailer(ctx context.Context, config runtimeInterfaces.NotificationsConfig, scope promutils.Scope, sm core.SecretManager) interfaces.Emailer { + var tlsConfiguration *tls.Config + emailConf := config.NotificationsEmailerConfig.EmailerConfig + + smtpPassword, err := sm.Get(ctx, emailConf.SmtpPasswordSecretName) + if err != nil { + logger.Debug(ctx, "No SMTP password found.") + smtpPassword = "" + } + + auth := smtp.PlainAuth("", emailConf.SmtpUsername, smtpPassword, emailConf.SmtpServer) + + tlsConfiguration = &tls.Config{ + InsecureSkipVerify: emailConf.SmtpSkipTLSVerify, + ServerName: emailConf.SmtpServer, + } + + return &SmtpEmailer{ + config: &config.NotificationsEmailerConfig, + systemMetrics: newEmailMetrics(scope.NewSubScope("smtp")), + tlsConf: tlsConfiguration, + auth: &auth, + } +} diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go new file mode 100644 index 0000000000..2896cf0e4a --- /dev/null +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go @@ -0,0 +1,60 @@ +package implementations + +import ( + "context" + "github.com/flyteorg/flyte/flyteadmin/pkg/runtime/interfaces" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/admin" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core/mocks" + "github.com/flyteorg/flyte/flytestdlib/promutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "testing" +) + +func getNotificationsEmailerConfig() interfaces.NotificationsConfig { + return interfaces.NotificationsConfig{ + Type: "", + Region: "", + AWSConfig: interfaces.AWSConfig{}, + GCPConfig: interfaces.GCPConfig{}, + NotificationsPublisherConfig: interfaces.NotificationsPublisherConfig{}, + NotificationsProcessorConfig: interfaces.NotificationsProcessorConfig{}, + NotificationsEmailerConfig: interfaces.NotificationsEmailerConfig{ + EmailerConfig: interfaces.EmailServerConfig{ + ServiceName: Smtp, + SmtpServer: "smtpServer", + SmtpPort: "smtpPort", + SmtpUsername: "smtpUsername", + SmtpPasswordSecretName: "smtp_password", + }, + Subject: "subject", + Sender: "sender", + Body: "body"}, + ReconnectAttempts: 1, + ReconnectDelaySeconds: 2} +} + +func TestEmailCreation(t *testing.T) { + email := admin.EmailMessage{ + RecipientsEmail: []string{"john@doe.com", "teresa@tester.com"}, + SubjectLine: "subject", + Body: "Email Body", + SenderEmail: "sender@sender.com", + } + + body, err := createMailBody("sender@sender.com", email) + + assert.NoError(t, err) + assert.Equal(t, "From: sender@sender.com\r\nTo: john@doe.com,teresa@tester.com\r\nSubject: subject\r\nContent-Type: text/html; charset=\"UTF-8\"\r\n\r\nEmail Body", body) +} + +func TestNewSmtpEmailer(t *testing.T) { + secretManagerMock := mocks.SecretManager{} + secretManagerMock.On("Get", mock.Anything, "smtp_password").Return("password", nil) + + notificationsConfig := getNotificationsEmailerConfig() + + smtpEmailer := NewSmtpEmailer(context.Background(), notificationsConfig, promutils.NewTestScope(), &secretManagerMock) + + assert.NotNil(t, smtpEmailer) +} diff --git a/flyteadmin/pkg/rpc/adminservice/base.go b/flyteadmin/pkg/rpc/adminservice/base.go index 8df2c595c7..1a9a31d758 100644 --- a/flyteadmin/pkg/rpc/adminservice/base.go +++ b/flyteadmin/pkg/rpc/adminservice/base.go @@ -3,6 +3,7 @@ package adminservice import ( "context" "fmt" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" "runtime/debug" "github.com/flyteorg/flyte/flyteadmin/pkg/async/cloudevent" @@ -45,7 +46,7 @@ type AdminService struct { const defaultRetries = 3 func NewAdminServer(ctx context.Context, pluginRegistry *plugins.Registry, configuration runtimeIfaces.Configuration, - kubeConfig, master string, dataStorageClient *storage.DataStore, adminScope promutils.Scope) *AdminService { + kubeConfig, master string, dataStorageClient *storage.DataStore, adminScope promutils.Scope, sm core.SecretManager) *AdminService { applicationConfiguration := configuration.ApplicationConfiguration().GetTopLevelConfig() panicCounter := adminScope.MustNewCounter("initialization_panic", @@ -81,7 +82,7 @@ func NewAdminServer(ctx context.Context, pluginRegistry *plugins.Registry, confi pluginRegistry.RegisterDefault(plugins.PluginIDWorkflowExecutor, workflowExecutor) publisher := notifications.NewNotificationsPublisher(*configuration.ApplicationConfiguration().GetNotificationsConfig(), adminScope) - processor := notifications.NewNotificationsProcessor(*configuration.ApplicationConfiguration().GetNotificationsConfig(), adminScope) + processor := notifications.NewNotificationsProcessor(*configuration.ApplicationConfiguration().GetNotificationsConfig(), adminScope, sm) eventPublisher := notifications.NewEventsPublisher(*configuration.ApplicationConfiguration().GetExternalEventsConfig(), adminScope) go func() { logger.Info(ctx, "Started processing notifications.") diff --git a/flyteadmin/pkg/runtime/interfaces/application_configuration.go b/flyteadmin/pkg/runtime/interfaces/application_configuration.go index 3505150919..d7b8fd783a 100644 --- a/flyteadmin/pkg/runtime/interfaces/application_configuration.go +++ b/flyteadmin/pkg/runtime/interfaces/application_configuration.go @@ -492,8 +492,13 @@ type NotificationsProcessorConfig struct { type EmailServerConfig struct { ServiceName string `json:"serviceName"` // Only one of these should be set. - APIKeyEnvVar string `json:"apiKeyEnvVar"` - APIKeyFilePath string `json:"apiKeyFilePath"` + APIKeyEnvVar string `json:"apiKeyEnvVar"` + APIKeyFilePath string `json:"apiKeyFilePath"` + SmtpServer string `json:"smtpServer"` + SmtpPort string `json:"smtpPort"` + SmtpSkipTLSVerify bool `json:"smtpSkipTLSVerify"` + SmtpUsername string `json:"smtpUsername"` + SmtpPasswordSecretName string `json:"smtpPasswordSecretName"` } // This section handles the configuration of notifications emails. diff --git a/flyteadmin/pkg/server/service.go b/flyteadmin/pkg/server/service.go index 587ea86e3b..b5c3bc6f2c 100644 --- a/flyteadmin/pkg/server/service.go +++ b/flyteadmin/pkg/server/service.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "fmt" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" "net" "net/http" "strings" @@ -82,7 +83,7 @@ func SetMetricKeys(appConfig *runtimeIfaces.ApplicationConfig) { // Creates a new gRPC Server with all the configuration func newGRPCServer(ctx context.Context, pluginRegistry *plugins.Registry, cfg *config.ServerConfig, storageCfg *storage.Config, authCtx interfaces.AuthenticationContext, - scope promutils.Scope, opts ...grpc.ServerOption) (*grpc.Server, error) { + scope promutils.Scope, sm core.SecretManager, opts ...grpc.ServerOption) (*grpc.Server, error) { logger.Infof(ctx, "Registering default middleware with blanket auth validation") pluginRegistry.RegisterDefault(plugins.PluginIDUnaryServiceMiddleware, grpcmiddleware.ChainUnaryServer( @@ -152,7 +153,7 @@ func newGRPCServer(ctx context.Context, pluginRegistry *plugins.Registry, cfg *c } configuration := runtime2.NewConfigurationProvider() - adminServer := adminservice.NewAdminServer(ctx, pluginRegistry, configuration, cfg.KubeConfig, cfg.Master, dataStorageClient, adminScope) + adminServer := adminservice.NewAdminServer(ctx, pluginRegistry, configuration, cfg.KubeConfig, cfg.Master, dataStorageClient, adminScope, sm) grpcService.RegisterAdminServiceServer(grpcServer, adminServer) if cfg.Security.UseAuth { grpcService.RegisterAuthMetadataServiceServer(grpcServer, authCtx.AuthMetadataService()) @@ -339,12 +340,15 @@ func serveGatewayInsecure(ctx context.Context, pluginRegistry *plugins.Registry, // This will parse configuration and create the necessary objects for dealing with auth var authCtx interfaces.AuthenticationContext var err error + + sm := secretmanager.NewFileEnvSecretManager(secretmanager.GetConfig()) + // This code is here to support authentication without SSL. This setup supports a network topology where // Envoy does the SSL termination. The final hop is made over localhost only on a trusted machine. // Warning: Running authentication without SSL in any other topology is a severe security flaw. // See the auth.Config object for additional settings as well. if cfg.Security.UseAuth { - sm := secretmanager.NewFileEnvSecretManager(secretmanager.GetConfig()) + var oauth2Provider interfaces.OAuth2Provider var oauth2ResourceServer interfaces.OAuth2ResourceServer if authCfg.AppAuth.AuthServerType == authConfig.AuthorizationServerTypeSelf { @@ -373,7 +377,7 @@ func serveGatewayInsecure(ctx context.Context, pluginRegistry *plugins.Registry, } } - grpcServer, err := newGRPCServer(ctx, pluginRegistry, cfg, storageConfig, authCtx, scope) + grpcServer, err := newGRPCServer(ctx, pluginRegistry, cfg, storageConfig, authCtx, scope, sm) if err != nil { return fmt.Errorf("failed to create a newGRPCServer. Error: %w", err) } @@ -448,13 +452,14 @@ func serveGatewaySecure(ctx context.Context, pluginRegistry *plugins.Registry, c additionalHandlers map[string]func(http.ResponseWriter, *http.Request), scope promutils.Scope) error { certPool, cert, err := GetSslCredentials(ctx, cfg.Security.Ssl.CertificateFile, cfg.Security.Ssl.KeyFile) + sm := secretmanager.NewFileEnvSecretManager(secretmanager.GetConfig()) + if err != nil { return err } // This will parse configuration and create the necessary objects for dealing with auth var authCtx interfaces.AuthenticationContext if cfg.Security.UseAuth { - sm := secretmanager.NewFileEnvSecretManager(secretmanager.GetConfig()) var oauth2Provider interfaces.OAuth2Provider var oauth2ResourceServer interfaces.OAuth2ResourceServer if authCfg.AppAuth.AuthServerType == authConfig.AuthorizationServerTypeSelf { @@ -483,7 +488,7 @@ func serveGatewaySecure(ctx context.Context, pluginRegistry *plugins.Registry, c } } - grpcServer, err := newGRPCServer(ctx, pluginRegistry, cfg, storageCfg, authCtx, scope, grpc.Creds(credentials.NewServerTLSFromCert(cert))) + grpcServer, err := newGRPCServer(ctx, pluginRegistry, cfg, storageCfg, authCtx, scope, sm, grpc.Creds(credentials.NewServerTLSFromCert(cert))) if err != nil { return fmt.Errorf("failed to create a newGRPCServer. Error: %w", err) } From a72042590de2d63526594370e42af86fd1f2f65f Mon Sep 17 00:00:00 2001 From: Rob Ulbrich Date: Wed, 3 Jul 2024 14:28:32 +0200 Subject: [PATCH 13/28] Fixing lint issues Signed-off-by: Rob Ulbrich --- flyteadmin/pkg/async/notifications/factory.go | 4 +-- .../notifications/implementations/emailers.go | 2 +- .../implementations/smtp_emailer.go | 32 ++++++++----------- .../implementations/smtp_emailer_test.go | 4 +-- .../interfaces/application_configuration.go | 10 +++--- 5 files changed, 23 insertions(+), 29 deletions(-) diff --git a/flyteadmin/pkg/async/notifications/factory.go b/flyteadmin/pkg/async/notifications/factory.go index 1a49340908..3468ae586f 100644 --- a/flyteadmin/pkg/async/notifications/factory.go +++ b/flyteadmin/pkg/async/notifications/factory.go @@ -58,8 +58,8 @@ func GetEmailer(config runtimeInterfaces.NotificationsConfig, scope promutils.Sc switch config.NotificationsEmailerConfig.EmailerConfig.ServiceName { case implementations.Sendgrid: return implementations.NewSendGridEmailer(config, scope) - case implementations.Smtp: - return implementations.NewSmtpEmailer(context.Background(), config, scope, sm) + case implementations.SMTP: + return implementations.NewSMTPEmailer(context.Background(), config, scope, sm) default: panic(fmt.Errorf("No matching email implementation for %s", config.NotificationsEmailerConfig.EmailerConfig.ServiceName)) } diff --git a/flyteadmin/pkg/async/notifications/implementations/emailers.go b/flyteadmin/pkg/async/notifications/implementations/emailers.go index bcef65424f..0da3fbf600 100644 --- a/flyteadmin/pkg/async/notifications/implementations/emailers.go +++ b/flyteadmin/pkg/async/notifications/implementations/emailers.go @@ -4,5 +4,5 @@ type ExternalEmailer = string const ( Sendgrid ExternalEmailer = "sendgrid" - Smtp ExternalEmailer = "smtp" + SMTP ExternalEmailer = "smtp" ) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go index 2306c6ec0e..90190d8a03 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go @@ -16,16 +16,16 @@ import ( "strings" ) -type SmtpEmailer struct { +type SMTPEmailer struct { config *runtimeInterfaces.NotificationsEmailerConfig systemMetrics emailMetrics tlsConf *tls.Config auth *smtp.Auth } -func (s *SmtpEmailer) SendEmail(ctx context.Context, email admin.EmailMessage) error { +func (s *SMTPEmailer) SendEmail(ctx context.Context, email admin.EmailMessage) error { - newClient, err := smtp.Dial(s.config.EmailerConfig.SmtpServer + ":" + s.config.EmailerConfig.SmtpPort) + newClient, err := smtp.Dial(s.config.EmailerConfig.SMTPServer + ":" + s.config.EmailerConfig.SMTPPort) if err != nil { return s.emailError(ctx, fmt.Sprintf("Error creating email client: %s", err)) @@ -65,13 +65,7 @@ func (s *SmtpEmailer) SendEmail(ctx context.Context, email admin.EmailMessage) e return s.emailError(ctx, fmt.Sprintf("Error adding email recipient: %s", err)) } - mailBody, err := createMailBody(s.config.Sender, email) - - if err != nil { - return s.emailError(ctx, fmt.Sprintf("Error creating email body: %s", err)) - } - - _, err = writer.Write([]byte(mailBody)) + _, err = writer.Write([]byte(createMailBody(s.config.Sender, email))) if err != nil { return s.emailError(ctx, fmt.Sprintf("Error writing mail body: %s", err)) @@ -93,13 +87,13 @@ func (s *SmtpEmailer) SendEmail(ctx context.Context, email admin.EmailMessage) e return nil } -func (s *SmtpEmailer) emailError(ctx context.Context, error string) error { +func (s *SMTPEmailer) emailError(ctx context.Context, error string) error { s.systemMetrics.SendError.Inc() logger.Error(ctx, error) return errors.NewFlyteAdminErrorf(codes.Internal, "errors were seen while sending emails") } -func createMailBody(emailSender string, email admin.EmailMessage) (string, error) { +func createMailBody(emailSender string, email admin.EmailMessage) string { headerMap := make(map[string]string) headerMap["From"] = emailSender headerMap["To"] = strings.Join(email.RecipientsEmail, ",") @@ -114,27 +108,27 @@ func createMailBody(emailSender string, email admin.EmailMessage) (string, error mailMessage += "\r\n" + email.Body - return mailMessage, nil + return mailMessage } -func NewSmtpEmailer(ctx context.Context, config runtimeInterfaces.NotificationsConfig, scope promutils.Scope, sm core.SecretManager) interfaces.Emailer { +func NewSMTPEmailer(ctx context.Context, config runtimeInterfaces.NotificationsConfig, scope promutils.Scope, sm core.SecretManager) interfaces.Emailer { var tlsConfiguration *tls.Config emailConf := config.NotificationsEmailerConfig.EmailerConfig - smtpPassword, err := sm.Get(ctx, emailConf.SmtpPasswordSecretName) + smtpPassword, err := sm.Get(ctx, emailConf.SMTPPasswordSecretName) if err != nil { logger.Debug(ctx, "No SMTP password found.") smtpPassword = "" } - auth := smtp.PlainAuth("", emailConf.SmtpUsername, smtpPassword, emailConf.SmtpServer) + auth := smtp.PlainAuth("", emailConf.SMTPUsername, smtpPassword, emailConf.SMTPServer) tlsConfiguration = &tls.Config{ - InsecureSkipVerify: emailConf.SmtpSkipTLSVerify, - ServerName: emailConf.SmtpServer, + InsecureSkipVerify: emailConf.SMTPSkipTLSVerify, + ServerName: emailConf.SMTPServer, } - return &SmtpEmailer{ + return &SMTPEmailer{ config: &config.NotificationsEmailerConfig, systemMetrics: newEmailMetrics(scope.NewSubScope("smtp")), tlsConf: tlsConfiguration, diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go index 2896cf0e4a..fc3593db10 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go @@ -21,7 +21,7 @@ func getNotificationsEmailerConfig() interfaces.NotificationsConfig { NotificationsProcessorConfig: interfaces.NotificationsProcessorConfig{}, NotificationsEmailerConfig: interfaces.NotificationsEmailerConfig{ EmailerConfig: interfaces.EmailServerConfig{ - ServiceName: Smtp, + ServiceName: SMTP, SmtpServer: "smtpServer", SmtpPort: "smtpPort", SmtpUsername: "smtpUsername", @@ -54,7 +54,7 @@ func TestNewSmtpEmailer(t *testing.T) { notificationsConfig := getNotificationsEmailerConfig() - smtpEmailer := NewSmtpEmailer(context.Background(), notificationsConfig, promutils.NewTestScope(), &secretManagerMock) + smtpEmailer := NewSMTPEmailer(context.Background(), notificationsConfig, promutils.NewTestScope(), &secretManagerMock) assert.NotNil(t, smtpEmailer) } diff --git a/flyteadmin/pkg/runtime/interfaces/application_configuration.go b/flyteadmin/pkg/runtime/interfaces/application_configuration.go index d7b8fd783a..e3453db0f7 100644 --- a/flyteadmin/pkg/runtime/interfaces/application_configuration.go +++ b/flyteadmin/pkg/runtime/interfaces/application_configuration.go @@ -494,11 +494,11 @@ type EmailServerConfig struct { // Only one of these should be set. APIKeyEnvVar string `json:"apiKeyEnvVar"` APIKeyFilePath string `json:"apiKeyFilePath"` - SmtpServer string `json:"smtpServer"` - SmtpPort string `json:"smtpPort"` - SmtpSkipTLSVerify bool `json:"smtpSkipTLSVerify"` - SmtpUsername string `json:"smtpUsername"` - SmtpPasswordSecretName string `json:"smtpPasswordSecretName"` + SMTPServer string `json:"smtpServer"` + SMTPPort string `json:"smtpPort"` + SMTPSkipTLSVerify bool `json:"smtpSkipTLSVerify"` + SMTPUsername string `json:"smtpUsername"` + SMTPPasswordSecretName string `json:"smtpPasswordSecretName"` } // This section handles the configuration of notifications emails. From 0bf96542e5027ba68f0f8aa7b2b79cd6de83de80 Mon Sep 17 00:00:00 2001 From: Rob Ulbrich Date: Thu, 4 Jul 2024 09:45:40 +0200 Subject: [PATCH 14/28] Fixing tests Signed-off-by: Rob Ulbrich --- .../implementations/smtp_emailer_test.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go index fc3593db10..ed56ad2e25 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go @@ -22,10 +22,10 @@ func getNotificationsEmailerConfig() interfaces.NotificationsConfig { NotificationsEmailerConfig: interfaces.NotificationsEmailerConfig{ EmailerConfig: interfaces.EmailServerConfig{ ServiceName: SMTP, - SmtpServer: "smtpServer", - SmtpPort: "smtpPort", - SmtpUsername: "smtpUsername", - SmtpPasswordSecretName: "smtp_password", + SMTPServer: "smtpServer", + SMTPPort: "smtpPort", + SMTPUsername: "smtpUsername", + SMTPPasswordSecretName: "smtp_password", }, Subject: "subject", Sender: "sender", @@ -42,9 +42,7 @@ func TestEmailCreation(t *testing.T) { SenderEmail: "sender@sender.com", } - body, err := createMailBody("sender@sender.com", email) - - assert.NoError(t, err) + body := createMailBody("sender@sender.com", email) assert.Equal(t, "From: sender@sender.com\r\nTo: john@doe.com,teresa@tester.com\r\nSubject: subject\r\nContent-Type: text/html; charset=\"UTF-8\"\r\n\r\nEmail Body", body) } From f513352c9d975830d60365d3a647ab0a4110f06b Mon Sep 17 00:00:00 2001 From: Rob Ulbrich Date: Thu, 4 Jul 2024 13:54:20 +0200 Subject: [PATCH 15/28] Fixing lint Signed-off-by: Rob Ulbrich --- .../pkg/async/notifications/implementations/smtp_emailer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go index 90190d8a03..3e68651e6e 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go @@ -123,7 +123,7 @@ func NewSMTPEmailer(ctx context.Context, config runtimeInterfaces.NotificationsC auth := smtp.PlainAuth("", emailConf.SMTPUsername, smtpPassword, emailConf.SMTPServer) - tlsConfiguration = &tls.Config{ + tlsConfiguration = &tls.Config{ // #nosec G402 InsecureSkipVerify: emailConf.SMTPSkipTLSVerify, ServerName: emailConf.SMTPServer, } From 1a0de4a86f7938dfcab88dd2a4dff32eef5b6b95 Mon Sep 17 00:00:00 2001 From: Rob Ulbrich Date: Fri, 5 Jul 2024 06:47:56 +0200 Subject: [PATCH 16/28] Fixing lint Signed-off-by: Rob Ulbrich --- .../pkg/async/notifications/implementations/smtp_emailer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go index 3e68651e6e..0eb67a41db 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go @@ -123,8 +123,8 @@ func NewSMTPEmailer(ctx context.Context, config runtimeInterfaces.NotificationsC auth := smtp.PlainAuth("", emailConf.SMTPUsername, smtpPassword, emailConf.SMTPServer) - tlsConfiguration = &tls.Config{ // #nosec G402 - InsecureSkipVerify: emailConf.SMTPSkipTLSVerify, + tlsConfiguration = &tls.Config{ + InsecureSkipVerify: emailConf.SMTPSkipTLSVerify, // #nosec G402 ServerName: emailConf.SMTPServer, } From dc88a9959247211ea01c7a97fac8cd900c183cae Mon Sep 17 00:00:00 2001 From: Rob Ulbrich Date: Mon, 15 Jul 2024 14:26:21 +0200 Subject: [PATCH 17/28] Fix linting issues Signed-off-by: Rob Ulbrich --- flyteadmin/pkg/async/notifications/factory.go | 3 ++- flyteadmin/pkg/async/notifications/factory_test.go | 5 ++--- .../pkg/async/notifications/implementations/smtp_emailer.go | 5 +++-- .../async/notifications/implementations/smtp_emailer_test.go | 3 ++- flyteadmin/pkg/rpc/adminservice/base.go | 3 ++- flyteadmin/pkg/server/service.go | 3 ++- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/flyteadmin/pkg/async/notifications/factory.go b/flyteadmin/pkg/async/notifications/factory.go index 3468ae586f..5698c3597e 100644 --- a/flyteadmin/pkg/async/notifications/factory.go +++ b/flyteadmin/pkg/async/notifications/factory.go @@ -3,10 +3,11 @@ package notifications import ( "context" "fmt" - "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" "sync" "time" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" + "github.com/NYTimes/gizmo/pubsub" gizmoAWS "github.com/NYTimes/gizmo/pubsub/aws" gizmoGCP "github.com/NYTimes/gizmo/pubsub/gcp" diff --git a/flyteadmin/pkg/async/notifications/factory_test.go b/flyteadmin/pkg/async/notifications/factory_test.go index 3505705766..72cdb88794 100644 --- a/flyteadmin/pkg/async/notifications/factory_test.go +++ b/flyteadmin/pkg/async/notifications/factory_test.go @@ -2,15 +2,14 @@ package notifications import ( "context" - "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core/mocks" "testing" - "github.com/stretchr/testify/assert" - "github.com/flyteorg/flyte/flyteadmin/pkg/async/notifications/implementations" runtimeInterfaces "github.com/flyteorg/flyte/flyteadmin/pkg/runtime/interfaces" "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/admin" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core/mocks" "github.com/flyteorg/flyte/flytestdlib/promutils" + "github.com/stretchr/testify/assert" ) var ( diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go index 0eb67a41db..7c079d2d77 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go @@ -3,6 +3,9 @@ package implementations import ( "crypto/tls" "fmt" + "net/smtp" + "strings" + "github.com/flyteorg/flyte/flyteadmin/pkg/async/notifications/interfaces" "github.com/flyteorg/flyte/flyteadmin/pkg/errors" runtimeInterfaces "github.com/flyteorg/flyte/flyteadmin/pkg/runtime/interfaces" @@ -12,8 +15,6 @@ import ( "github.com/flyteorg/flyte/flytestdlib/promutils" "golang.org/x/net/context" "google.golang.org/grpc/codes" - "net/smtp" - "strings" ) type SMTPEmailer struct { diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go index ed56ad2e25..be664331f9 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go @@ -2,13 +2,14 @@ package implementations import ( "context" + "testing" + "github.com/flyteorg/flyte/flyteadmin/pkg/runtime/interfaces" "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/admin" "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core/mocks" "github.com/flyteorg/flyte/flytestdlib/promutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "testing" ) func getNotificationsEmailerConfig() interfaces.NotificationsConfig { diff --git a/flyteadmin/pkg/rpc/adminservice/base.go b/flyteadmin/pkg/rpc/adminservice/base.go index 1a9a31d758..b638b8e3cb 100644 --- a/flyteadmin/pkg/rpc/adminservice/base.go +++ b/flyteadmin/pkg/rpc/adminservice/base.go @@ -3,7 +3,6 @@ package adminservice import ( "context" "fmt" - "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" "runtime/debug" "github.com/flyteorg/flyte/flyteadmin/pkg/async/cloudevent" @@ -21,9 +20,11 @@ import ( workflowengineImpl "github.com/flyteorg/flyte/flyteadmin/pkg/workflowengine/impl" "github.com/flyteorg/flyte/flyteadmin/plugins" "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/service" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" "github.com/flyteorg/flyte/flytestdlib/logger" "github.com/flyteorg/flyte/flytestdlib/promutils" "github.com/flyteorg/flyte/flytestdlib/storage" + "github.com/golang/protobuf/proto" ) type AdminService struct { diff --git a/flyteadmin/pkg/server/service.go b/flyteadmin/pkg/server/service.go index b5c3bc6f2c..32b63c9273 100644 --- a/flyteadmin/pkg/server/service.go +++ b/flyteadmin/pkg/server/service.go @@ -4,12 +4,13 @@ import ( "context" "crypto/tls" "fmt" - "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" "net" "net/http" "strings" "time" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" + "github.com/gorilla/handlers" grpcmiddleware "github.com/grpc-ecosystem/go-grpc-middleware" grpcauth "github.com/grpc-ecosystem/go-grpc-middleware/auth" From b54a9328b64d2608db414998428888eb05aafed7 Mon Sep 17 00:00:00 2001 From: Ulbrich Robert Date: Fri, 16 Aug 2024 10:40:27 +0200 Subject: [PATCH 18/28] Fix go mod Signed-off-by: Ulbrich Robert --- flyteadmin/go.mod | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/flyteadmin/go.mod b/flyteadmin/go.mod index 1ff9cd2464..2eec0f8cf3 100644 --- a/flyteadmin/go.mod +++ b/flyteadmin/go.mod @@ -51,7 +51,7 @@ require ( github.com/wolfeidau/humanhash v1.1.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 go.opentelemetry.io/otel v1.24.0 - golang.org/x/net v0.23.0 + golang.org/x/net v0.27.0 golang.org/x/oauth2 v0.16.0 golang.org/x/time v0.5.0 google.golang.org/api v0.155.0 @@ -190,7 +190,6 @@ require ( go.opentelemetry.io/proto/otlp v1.1.0 // indirect golang.org/x/crypto v0.25.0 // indirect golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect - golang.org/x/net v0.27.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/term v0.22.0 // indirect From 5eac9ea5d24ab9ac2be5bb4aa90a47fe78fb877e Mon Sep 17 00:00:00 2001 From: Ulbrich Robert Date: Thu, 5 Sep 2024 11:22:48 +0200 Subject: [PATCH 19/28] Fixing location of no sec comment Signed-off-by: Ulbrich Robert --- .../pkg/async/notifications/implementations/smtp_emailer.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go index 7c079d2d77..dce1adfd00 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go @@ -124,8 +124,9 @@ func NewSMTPEmailer(ctx context.Context, config runtimeInterfaces.NotificationsC auth := smtp.PlainAuth("", emailConf.SMTPUsername, smtpPassword, emailConf.SMTPServer) + // #nosec G402 tlsConfiguration = &tls.Config{ - InsecureSkipVerify: emailConf.SMTPSkipTLSVerify, // #nosec G402 + InsecureSkipVerify: emailConf.SMTPSkipTLSVerify, ServerName: emailConf.SMTPServer, } From 0926604e9b2e9b570b9054a69f0659b23918e6e2 Mon Sep 17 00:00:00 2001 From: Ulbrich Robert Date: Thu, 5 Sep 2024 12:30:38 +0200 Subject: [PATCH 20/28] Removing unused import Signed-off-by: Ulbrich Robert --- flyteadmin/pkg/rpc/adminservice/base.go | 1 - 1 file changed, 1 deletion(-) diff --git a/flyteadmin/pkg/rpc/adminservice/base.go b/flyteadmin/pkg/rpc/adminservice/base.go index b638b8e3cb..491a24a1f0 100644 --- a/flyteadmin/pkg/rpc/adminservice/base.go +++ b/flyteadmin/pkg/rpc/adminservice/base.go @@ -24,7 +24,6 @@ import ( "github.com/flyteorg/flyte/flytestdlib/logger" "github.com/flyteorg/flyte/flytestdlib/promutils" "github.com/flyteorg/flyte/flytestdlib/storage" - "github.com/golang/protobuf/proto" ) type AdminService struct { From 9d72fac22e80ea99c5db1d50e696989c3c87cb24 Mon Sep 17 00:00:00 2001 From: Ulbrich Robert Date: Mon, 9 Sep 2024 15:19:35 +0200 Subject: [PATCH 21/28] Running gci for file Signed-off-by: Ulbrich Robert --- .../async/notifications/implementations/smtp_emailer_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go index be664331f9..320a3c6ea4 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go @@ -4,12 +4,13 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/flyteorg/flyte/flyteadmin/pkg/runtime/interfaces" "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/admin" "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core/mocks" "github.com/flyteorg/flyte/flytestdlib/promutils" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" ) func getNotificationsEmailerConfig() interfaces.NotificationsConfig { From d995db797796a5bab865ae1ccf073700798bd552 Mon Sep 17 00:00:00 2001 From: Ulbrich Robert Date: Tue, 10 Sep 2024 14:26:58 +0200 Subject: [PATCH 22/28] Implemented connection reuse for smtp emailer Signed-off-by: Ulbrich Robert --- .../implementations/smtp_emailer.go | 53 ++++++++++++------- flyteadmin/pkg/server/service.go | 3 +- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go index dce1adfd00..94fa82577f 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go @@ -6,6 +6,9 @@ import ( "net/smtp" "strings" + "golang.org/x/net/context" + "google.golang.org/grpc/codes" + "github.com/flyteorg/flyte/flyteadmin/pkg/async/notifications/interfaces" "github.com/flyteorg/flyte/flyteadmin/pkg/errors" runtimeInterfaces "github.com/flyteorg/flyte/flyteadmin/pkg/runtime/interfaces" @@ -13,8 +16,6 @@ import ( "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" "github.com/flyteorg/flyte/flytestdlib/logger" "github.com/flyteorg/flyte/flytestdlib/promutils" - "golang.org/x/net/context" - "google.golang.org/grpc/codes" ) type SMTPEmailer struct { @@ -22,45 +23,65 @@ type SMTPEmailer struct { systemMetrics emailMetrics tlsConf *tls.Config auth *smtp.Auth + smtpClient *smtp.Client } -func (s *SMTPEmailer) SendEmail(ctx context.Context, email admin.EmailMessage) error { - +func (s *SMTPEmailer) createClient(ctx context.Context) (*smtp.Client, error) { newClient, err := smtp.Dial(s.config.EmailerConfig.SMTPServer + ":" + s.config.EmailerConfig.SMTPPort) if err != nil { - return s.emailError(ctx, fmt.Sprintf("Error creating email client: %s", err)) + return nil, s.emailError(ctx, fmt.Sprintf("Error creating email client: %s", err)) } - defer newClient.Close() - if err = newClient.Hello("localhost"); err != nil { - return s.emailError(ctx, fmt.Sprintf("Error initiating connection to SMTP server: %s", err)) + return nil, s.emailError(ctx, fmt.Sprintf("Error initiating connection to SMTP server: %s", err)) } if ok, _ := newClient.Extension("STARTTLS"); ok { if err = newClient.StartTLS(s.tlsConf); err != nil { - return err + return nil, err } } if ok, _ := newClient.Extension("AUTH"); ok { if err = newClient.Auth(*s.auth); err != nil { - return s.emailError(ctx, fmt.Sprintf("Error authenticating email client: %s", err)) + return nil, s.emailError(ctx, fmt.Sprintf("Error authenticating email client: %s", err)) } } - if err = newClient.Mail(email.SenderEmail); err != nil { + return newClient, nil +} + +func (s *SMTPEmailer) SendEmail(ctx context.Context, email admin.EmailMessage) error { + + if s.smtpClient == nil || s.smtpClient.Noop() != nil { + + if s.smtpClient != nil { + err := s.smtpClient.Close() + if err != nil { + logger.Info(ctx, err) + } + } + smtpClient, err := s.createClient(ctx) + + if err != nil { + return s.emailError(ctx, fmt.Sprintf("Error creating SMTP email client: %s", err)) + } + + s.smtpClient = smtpClient + } + + if err := s.smtpClient.Mail(email.SenderEmail); err != nil { return s.emailError(ctx, fmt.Sprintf("Error creating email instance: %s", err)) } for _, recipient := range email.RecipientsEmail { - if err = newClient.Rcpt(recipient); err != nil { + if err := s.smtpClient.Rcpt(recipient); err != nil { logger.Errorf(ctx, "Error adding email recipient: %s", err) } } - writer, err := newClient.Data() + writer, err := s.smtpClient.Data() if err != nil { return s.emailError(ctx, fmt.Sprintf("Error adding email recipient: %s", err)) @@ -78,12 +99,6 @@ func (s *SMTPEmailer) SendEmail(ctx context.Context, email admin.EmailMessage) e return s.emailError(ctx, fmt.Sprintf("Error closing mail body: %s", err)) } - err = newClient.Quit() - - if err != nil { - return s.emailError(ctx, fmt.Sprintf("Error quitting mail agent: %s", err)) - } - s.systemMetrics.SendSuccess.Inc() return nil } diff --git a/flyteadmin/pkg/server/service.go b/flyteadmin/pkg/server/service.go index 32b63c9273..840d0d9f17 100644 --- a/flyteadmin/pkg/server/service.go +++ b/flyteadmin/pkg/server/service.go @@ -9,8 +9,6 @@ import ( "strings" "time" - "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" - "github.com/gorilla/handlers" grpcmiddleware "github.com/grpc-ecosystem/go-grpc-middleware" grpcauth "github.com/grpc-ecosystem/go-grpc-middleware/auth" @@ -45,6 +43,7 @@ import ( "github.com/flyteorg/flyte/flyteidl/clients/go/assets" grpcService "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/service" "github.com/flyteorg/flyte/flyteidl/gen/pb-go/gateway/flyteidl/service" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" "github.com/flyteorg/flyte/flytepropeller/pkg/controller/nodes/task/secretmanager" "github.com/flyteorg/flyte/flytestdlib/contextutils" "github.com/flyteorg/flyte/flytestdlib/logger" From 5da05e11a2318a9f96981ece995edd760f238fb7 Mon Sep 17 00:00:00 2001 From: Ulbrich Robert Date: Wed, 11 Sep 2024 07:15:16 +0200 Subject: [PATCH 23/28] Adapting interface of smtp_emailer Signed-off-by: Ulbrich Robert --- .../pkg/async/notifications/implementations/smtp_emailer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go index 94fa82577f..b28b0550d9 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go @@ -52,7 +52,7 @@ func (s *SMTPEmailer) createClient(ctx context.Context) (*smtp.Client, error) { return newClient, nil } -func (s *SMTPEmailer) SendEmail(ctx context.Context, email admin.EmailMessage) error { +func (s *SMTPEmailer) SendEmail(ctx context.Context, email *admin.EmailMessage) error { if s.smtpClient == nil || s.smtpClient.Noop() != nil { @@ -109,7 +109,7 @@ func (s *SMTPEmailer) emailError(ctx context.Context, error string) error { return errors.NewFlyteAdminErrorf(codes.Internal, "errors were seen while sending emails") } -func createMailBody(emailSender string, email admin.EmailMessage) string { +func createMailBody(emailSender string, email *admin.EmailMessage) string { headerMap := make(map[string]string) headerMap["From"] = emailSender headerMap["To"] = strings.Join(email.RecipientsEmail, ",") From 1050f4b3fc118d39f3e2fe76e28c2690c27261a7 Mon Sep 17 00:00:00 2001 From: Ulbrich Robert Date: Thu, 12 Sep 2024 06:48:24 +0200 Subject: [PATCH 24/28] Fixing linter issue Signed-off-by: Ulbrich Robert --- .../async/notifications/implementations/smtp_emailer_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go index 320a3c6ea4..1fd3a75e00 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go @@ -44,7 +44,7 @@ func TestEmailCreation(t *testing.T) { SenderEmail: "sender@sender.com", } - body := createMailBody("sender@sender.com", email) + body := createMailBody("sender@sender.com", &email) assert.Equal(t, "From: sender@sender.com\r\nTo: john@doe.com,teresa@tester.com\r\nSubject: subject\r\nContent-Type: text/html; charset=\"UTF-8\"\r\n\r\nEmail Body", body) } From 6d0e4bf3ca4b25914ccee5fa394276923059a69d Mon Sep 17 00:00:00 2001 From: Ulbrich Robert Date: Thu, 12 Sep 2024 11:57:57 +0200 Subject: [PATCH 25/28] Adding unit tests Signed-off-by: Ulbrich Robert --- flyteadmin/pkg/async/notifications/factory.go | 190 ++++++- .../pkg/async/notifications/factory_test.go | 30 +- .../implementations/smtp_emailer.go | 22 +- .../implementations/smtp_emailer_test.go | 436 ++++++++++++++++ .../notifications/interfaces/smtp_client.go | 22 + .../async/notifications/mocks/smtp_client.go | 472 ++++++++++++++++++ 6 files changed, 1156 insertions(+), 16 deletions(-) create mode 100644 flyteadmin/pkg/async/notifications/interfaces/smtp_client.go create mode 100644 flyteadmin/pkg/async/notifications/mocks/smtp_client.go diff --git a/flyteadmin/pkg/async/notifications/factory.go b/flyteadmin/pkg/async/notifications/factory.go index 5698c3597e..483978238e 100644 --- a/flyteadmin/pkg/async/notifications/factory.go +++ b/flyteadmin/pkg/async/notifications/factory.go @@ -6,8 +6,6 @@ import ( "sync" "time" - "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" - "github.com/NYTimes/gizmo/pubsub" gizmoAWS "github.com/NYTimes/gizmo/pubsub/aws" gizmoGCP "github.com/NYTimes/gizmo/pubsub/gcp" @@ -20,6 +18,7 @@ import ( "github.com/flyteorg/flyte/flyteadmin/pkg/async/notifications/interfaces" "github.com/flyteorg/flyte/flyteadmin/pkg/common" runtimeInterfaces "github.com/flyteorg/flyte/flyteadmin/pkg/runtime/interfaces" + "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core" "github.com/flyteorg/flyte/flytestdlib/logger" "github.com/flyteorg/flyte/flytestdlib/promutils" ) @@ -29,6 +28,7 @@ const maxRetries = 3 var enable64decoding = false var msgChan chan []byte + var once sync.Once type PublisherConfig struct { @@ -37,222 +37,404 @@ type PublisherConfig struct { type ProcessorConfig struct { QueueName string + AccountID string } type EmailerConfig struct { SenderEmail string - BaseURL string + + BaseURL string } // For sandbox only + func CreateMsgChan() { + once.Do(func() { + msgChan = make(chan []byte) + }) + } func GetEmailer(config runtimeInterfaces.NotificationsConfig, scope promutils.Scope, sm core.SecretManager) interfaces.Emailer { + // If an external email service is specified use that instead. + // TODO: Handling of this is messy, see https://github.com/flyteorg/flyte/issues/1063 + if config.NotificationsEmailerConfig.EmailerConfig.ServiceName != "" { + switch config.NotificationsEmailerConfig.EmailerConfig.ServiceName { + case implementations.Sendgrid: + return implementations.NewSendGridEmailer(config, scope) + case implementations.SMTP: + return implementations.NewSMTPEmailer(context.Background(), config, scope, sm) + default: + panic(fmt.Errorf("No matching email implementation for %s", config.NotificationsEmailerConfig.EmailerConfig.ServiceName)) + } + } switch config.Type { + case common.AWS: + region := config.AWSConfig.Region + if region == "" { + region = config.Region + } + awsConfig := aws.NewConfig().WithRegion(region).WithMaxRetries(maxRetries) + awsSession, err := session.NewSession(awsConfig) + if err != nil { + panic(err) + } + sesClient := ses.New(awsSession) + return implementations.NewAwsEmailer( + config, + scope, + sesClient, ) + case common.Local: + fallthrough + default: + logger.Infof(context.Background(), "Using default noop emailer implementation for config type [%s]", config.Type) + return implementations.NewNoopEmail() + } + } func NewNotificationsProcessor(config runtimeInterfaces.NotificationsConfig, scope promutils.Scope, sm core.SecretManager) interfaces.Processor { + reconnectAttempts := config.ReconnectAttempts + reconnectDelay := time.Duration(config.ReconnectDelaySeconds) * time.Second + var sub pubsub.Subscriber + var emailer interfaces.Emailer + switch config.Type { + case common.AWS: + sqsConfig := gizmoAWS.SQSConfig{ - QueueName: config.NotificationsProcessorConfig.QueueName, + + QueueName: config.NotificationsProcessorConfig.QueueName, + QueueOwnerAccountID: config.NotificationsProcessorConfig.AccountID, + // The AWS configuration type uses SNS to SQS for notifications. + // Gizmo by default will decode the SQS message using Base64 decoding. + // However, the message body of SQS is the SNS message format which isn't Base64 encoded. + ConsumeBase64: &enable64decoding, } + if config.AWSConfig.Region != "" { + sqsConfig.Region = config.AWSConfig.Region + } else { + sqsConfig.Region = config.Region + } + var err error + err = async.Retry(reconnectAttempts, reconnectDelay, func() error { + sub, err = gizmoAWS.NewSubscriber(sqsConfig) + if err != nil { + logger.Warnf(context.TODO(), "Failed to initialize new gizmo aws subscriber with config [%+v] and err: %v", sqsConfig, err) + } + return err + }) if err != nil { + panic(err) + } + emailer = GetEmailer(config, scope, sm) + return implementations.NewProcessor(sub, emailer, scope) + case common.GCP: + projectID := config.GCPConfig.ProjectID + subscription := config.NotificationsProcessorConfig.QueueName + var err error + err = async.Retry(reconnectAttempts, reconnectDelay, func() error { + sub, err = gizmoGCP.NewSubscriber(context.TODO(), projectID, subscription) + if err != nil { + logger.Warnf(context.TODO(), "Failed to initialize new gizmo gcp subscriber with config [ProjectID: %s, Subscription: %s] and err: %v", projectID, subscription, err) + } + return err + }) + if err != nil { + panic(err) + } + emailer = GetEmailer(config, scope, sm) + return implementations.NewGcpProcessor(sub, emailer, scope) + case common.Sandbox: + emailer = GetEmailer(config, scope, sm) + return implementations.NewSandboxProcessor(msgChan, emailer) + case common.Local: + fallthrough + default: + logger.Infof(context.Background(), + "Using default noop notifications processor implementation for config type [%s]", config.Type) + return implementations.NewNoopProcess() + } + } func NewNotificationsPublisher(config runtimeInterfaces.NotificationsConfig, scope promutils.Scope) interfaces.Publisher { + reconnectAttempts := config.ReconnectAttempts + reconnectDelay := time.Duration(config.ReconnectDelaySeconds) * time.Second + switch config.Type { + case common.AWS: + snsConfig := gizmoAWS.SNSConfig{ + Topic: config.NotificationsPublisherConfig.TopicName, } + if config.AWSConfig.Region != "" { + snsConfig.Region = config.AWSConfig.Region + } else { + snsConfig.Region = config.Region + } var publisher pubsub.Publisher + var err error + err = async.Retry(reconnectAttempts, reconnectDelay, func() error { + publisher, err = gizmoAWS.NewPublisher(snsConfig) + return err + }) // Any persistent errors initiating Publisher with Amazon configurations results in a failed start up. + if err != nil { + panic(err) + } + return implementations.NewPublisher(publisher, scope) + case common.GCP: + pubsubConfig := gizmoGCP.Config{ + Topic: config.NotificationsPublisherConfig.TopicName, } + pubsubConfig.ProjectID = config.GCPConfig.ProjectID + var publisher pubsub.MultiPublisher + var err error + err = async.Retry(reconnectAttempts, reconnectDelay, func() error { + publisher, err = gizmoGCP.NewPublisher(context.TODO(), pubsubConfig) + return err + }) if err != nil { + panic(err) + } + return implementations.NewPublisher(publisher, scope) + case common.Sandbox: + CreateMsgChan() + return implementations.NewSandboxPublisher(msgChan) + case common.Local: + fallthrough + default: + logger.Infof(context.Background(), + "Using default noop notifications publisher implementation for config type [%s]", config.Type) + return implementations.NewNoopPublish() + } + } func NewEventsPublisher(config runtimeInterfaces.ExternalEventsConfig, scope promutils.Scope) interfaces.Publisher { + if !config.Enable { + return implementations.NewNoopPublish() + } + reconnectAttempts := config.ReconnectAttempts + reconnectDelay := time.Duration(config.ReconnectDelaySeconds) * time.Second + switch config.Type { + case common.AWS: + snsConfig := gizmoAWS.SNSConfig{ + Topic: config.EventsPublisherConfig.TopicName, } + snsConfig.Region = config.AWSConfig.Region var publisher pubsub.Publisher + var err error + err = async.Retry(reconnectAttempts, reconnectDelay, func() error { + publisher, err = gizmoAWS.NewPublisher(snsConfig) + return err + }) // Any persistent errors initiating Publisher with Amazon configurations results in a failed start up. + if err != nil { + panic(err) + } + return implementations.NewEventsPublisher(publisher, scope, config.EventsPublisherConfig.EventTypes) + case common.GCP: + pubsubConfig := gizmoGCP.Config{ + Topic: config.EventsPublisherConfig.TopicName, } + pubsubConfig.ProjectID = config.GCPConfig.ProjectID + var publisher pubsub.MultiPublisher + var err error + err = async.Retry(reconnectAttempts, reconnectDelay, func() error { + publisher, err = gizmoGCP.NewPublisher(context.TODO(), pubsubConfig) + return err + }) if err != nil { + panic(err) + } + return implementations.NewEventsPublisher(publisher, scope, config.EventsPublisherConfig.EventTypes) + case common.Local: + fallthrough + default: + logger.Infof(context.Background(), + "Using default noop events publisher implementation for config type [%s]", config.Type) + return implementations.NewNoopPublish() + } + } diff --git a/flyteadmin/pkg/async/notifications/factory_test.go b/flyteadmin/pkg/async/notifications/factory_test.go index 72cdb88794..43602525a5 100644 --- a/flyteadmin/pkg/async/notifications/factory_test.go +++ b/flyteadmin/pkg/async/notifications/factory_test.go @@ -4,35 +4,50 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" + "github.com/flyteorg/flyte/flyteadmin/pkg/async/notifications/implementations" runtimeInterfaces "github.com/flyteorg/flyte/flyteadmin/pkg/runtime/interfaces" "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/admin" "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core/mocks" "github.com/flyteorg/flyte/flytestdlib/promutils" - "github.com/stretchr/testify/assert" ) var ( - scope = promutils.NewScope("test_sandbox_processor") + scope = promutils.NewScope("test_sandbox_processor") + notificationsConfig = runtimeInterfaces.NotificationsConfig{ + Type: "sandbox", } + testEmail = admin.EmailMessage{ + RecipientsEmail: []string{ + "a@example.com", + "b@example.com", }, + SenderEmail: "no-reply@example.com", + SubjectLine: "Test email", - Body: "This is a sample email.", + + Body: "This is a sample email.", } ) func TestGetEmailer(t *testing.T) { + defer func() { r := recover(); assert.NotNil(t, r) }() + cfg := runtimeInterfaces.NotificationsConfig{ + NotificationsEmailerConfig: runtimeInterfaces.NotificationsEmailerConfig{ + EmailerConfig: runtimeInterfaces.EmailServerConfig{ + ServiceName: "unsupported", }, }, @@ -41,20 +56,29 @@ func TestGetEmailer(t *testing.T) { GetEmailer(cfg, promutils.NewTestScope(), &mocks.SecretManager{}) // shouldn't reach here + t.Errorf("did not panic") + } func TestNewNotificationPublisherAndProcessor(t *testing.T) { + testSandboxPublisher := NewNotificationsPublisher(notificationsConfig, scope) + assert.IsType(t, testSandboxPublisher, &implementations.SandboxPublisher{}) + testSandboxProcessor := NewNotificationsProcessor(notificationsConfig, scope, &mocks.SecretManager{}) + assert.IsType(t, testSandboxProcessor, &implementations.SandboxProcessor{}) go func() { + testSandboxProcessor.StartProcessing() + }() assert.Nil(t, testSandboxPublisher.Publish(context.Background(), "TEST_NOTIFICATION", &testEmail)) assert.Nil(t, testSandboxProcessor.StopProcessing()) + } diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go index b28b0550d9..5a705bc0c1 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer.go @@ -19,15 +19,16 @@ import ( ) type SMTPEmailer struct { - config *runtimeInterfaces.NotificationsEmailerConfig - systemMetrics emailMetrics - tlsConf *tls.Config - auth *smtp.Auth - smtpClient *smtp.Client + config *runtimeInterfaces.NotificationsEmailerConfig + systemMetrics emailMetrics + tlsConf *tls.Config + auth *smtp.Auth + smtpClient interfaces.SMTPClient + CreateSMTPClientFunc func(connectString string) (interfaces.SMTPClient, error) } -func (s *SMTPEmailer) createClient(ctx context.Context) (*smtp.Client, error) { - newClient, err := smtp.Dial(s.config.EmailerConfig.SMTPServer + ":" + s.config.EmailerConfig.SMTPPort) +func (s *SMTPEmailer) createClient(ctx context.Context) (interfaces.SMTPClient, error) { + newClient, err := s.CreateSMTPClientFunc(s.config.EmailerConfig.SMTPServer + ":" + s.config.EmailerConfig.SMTPPort) if err != nil { return nil, s.emailError(ctx, fmt.Sprintf("Error creating email client: %s", err)) @@ -39,7 +40,7 @@ func (s *SMTPEmailer) createClient(ctx context.Context) (*smtp.Client, error) { if ok, _ := newClient.Extension("STARTTLS"); ok { if err = newClient.StartTLS(s.tlsConf); err != nil { - return nil, err + return nil, s.emailError(ctx, fmt.Sprintf("Error initiating connection to SMTP server: %s", err)) } } @@ -77,7 +78,7 @@ func (s *SMTPEmailer) SendEmail(ctx context.Context, email *admin.EmailMessage) for _, recipient := range email.RecipientsEmail { if err := s.smtpClient.Rcpt(recipient); err != nil { - logger.Errorf(ctx, "Error adding email recipient: %s", err) + return s.emailError(ctx, fmt.Sprintf("Error adding email recipient: %s", err)) } } @@ -150,5 +151,8 @@ func NewSMTPEmailer(ctx context.Context, config runtimeInterfaces.NotificationsC systemMetrics: newEmailMetrics(scope.NewSubScope("smtp")), tlsConf: tlsConfiguration, auth: &auth, + CreateSMTPClientFunc: func(connectString string) (interfaces.SMTPClient, error) { + return smtp.Dial(connectString) + }, } } diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go index 1fd3a75e00..924bd1278c 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go @@ -2,17 +2,42 @@ package implementations import ( "context" + "crypto/tls" + "errors" + "google.golang.org/grpc/codes" + "net/smtp" + "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + notification_interfaces "github.com/flyteorg/flyte/flyteadmin/pkg/async/notifications/interfaces" + notification_mocks "github.com/flyteorg/flyte/flyteadmin/pkg/async/notifications/mocks" + + flyte_errors "github.com/flyteorg/flyte/flyteadmin/pkg/errors" + "github.com/flyteorg/flyte/flyteadmin/pkg/runtime/interfaces" "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/admin" "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core/mocks" "github.com/flyteorg/flyte/flytestdlib/promutils" ) +type StringWriter struct { + buffer string + writeErr error + closeErr error +} + +func (s *StringWriter) Write(p []byte) (n int, err error) { + s.buffer = s.buffer + string(p) + return len(p), s.writeErr +} + +func (s *StringWriter) Close() error { + return s.closeErr +} + func getNotificationsEmailerConfig() interfaces.NotificationsConfig { return interfaces.NotificationsConfig{ Type: "", @@ -58,3 +83,414 @@ func TestNewSmtpEmailer(t *testing.T) { assert.NotNil(t, smtpEmailer) } + +func TestCreateClient(t *testing.T) { + auth := smtp.PlainAuth("", "user", "password", "localhost") + + tlsConf := tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + } + + smtpClient := notification_mocks.NewSMTPClient(t) + smtpClient.EXPECT().Hello("localhost").Return(nil).Times(1) + smtpClient.EXPECT().Extension("STARTTLS").Return(true, "").Times(1) + smtpClient.EXPECT().StartTLS(&tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + }).Return(nil).Times(1) + smtpClient.EXPECT().Extension("AUTH").Return(true, "").Times(1) + smtpClient.EXPECT().Auth(auth).Return(nil).Times(1) + + smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) + + client, err := smtpEmailer.createClient(context.Background()) + + assert.Nil(t, err) + assert.NotNil(t, client) + +} + +func TestCreateClientErrorCreatingClient(t *testing.T) { + auth := smtp.PlainAuth("", "user", "password", "localhost") + + tlsConf := tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + } + + smtpClient := notification_mocks.NewSMTPClient(t) + + smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, errors.New("error creating client")) + + client, err := smtpEmailer.createClient(context.Background()) + + assert.Equal(t, flyte_errors.NewFlyteAdminErrorf(codes.Internal, "errors were seen while sending emails"), err) + assert.Nil(t, client) + +} + +func TestCreateClientErrorHello(t *testing.T) { + auth := smtp.PlainAuth("", "user", "password", "localhost") + + tlsConf := tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + } + + smtpClient := notification_mocks.NewSMTPClient(t) + smtpClient.EXPECT().Hello("localhost").Return(errors.New("Error with hello")).Times(1) + + smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) + + client, err := smtpEmailer.createClient(context.Background()) + + assert.Equal(t, flyte_errors.NewFlyteAdminErrorf(codes.Internal, "errors were seen while sending emails"), err) + assert.Nil(t, client) + +} + +func TestCreateClientErrorStartTLS(t *testing.T) { + auth := smtp.PlainAuth("", "user", "password", "localhost") + + tlsConf := tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + } + + smtpClient := notification_mocks.NewSMTPClient(t) + smtpClient.EXPECT().Hello("localhost").Return(nil).Times(1) + smtpClient.EXPECT().Extension("STARTTLS").Return(true, "").Times(1) + smtpClient.EXPECT().StartTLS(&tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + }).Return(errors.New("Error with startls")).Times(1) + + smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) + + client, err := smtpEmailer.createClient(context.Background()) + + assert.Equal(t, flyte_errors.NewFlyteAdminErrorf(codes.Internal, "errors were seen while sending emails"), err) + assert.Nil(t, client) + +} + +func TestCreateClientErrorAuth(t *testing.T) { + auth := smtp.PlainAuth("", "user", "password", "localhost") + + tlsConf := tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + } + + smtpClient := notification_mocks.NewSMTPClient(t) + smtpClient.EXPECT().Hello("localhost").Return(nil).Times(1) + smtpClient.EXPECT().Extension("STARTTLS").Return(true, "").Times(1) + smtpClient.EXPECT().StartTLS(&tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + }).Return(nil).Times(1) + smtpClient.EXPECT().Extension("AUTH").Return(true, "").Times(1) + smtpClient.EXPECT().Auth(auth).Return(errors.New("Error with hello")).Times(1) + + smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) + + client, err := smtpEmailer.createClient(context.Background()) + + assert.Equal(t, flyte_errors.NewFlyteAdminErrorf(codes.Internal, "errors were seen while sending emails"), err) + assert.Nil(t, client) + +} + +func TestSendMail(t *testing.T) { + auth := smtp.PlainAuth("", "user", "password", "localhost") + + tlsConf := tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + } + + stringWriter := StringWriter{buffer: ""} + + smtpClient := notification_mocks.NewSMTPClient(t) + smtpClient.EXPECT().Noop().Return(errors.New("no connection")).Times(1) + smtpClient.EXPECT().Close().Return(nil).Times(1) + smtpClient.EXPECT().Hello("localhost").Return(nil).Times(1) + smtpClient.EXPECT().Extension("STARTTLS").Return(true, "").Times(1) + smtpClient.EXPECT().StartTLS(&tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + }).Return(nil).Times(1) + smtpClient.EXPECT().Extension("AUTH").Return(true, "").Times(1) + smtpClient.EXPECT().Auth(auth).Return(nil).Times(1) + smtpClient.EXPECT().Mail("flyte@flyte.org").Return(nil).Times(1) + smtpClient.EXPECT().Rcpt("alice@flyte.org").Return(nil).Times(1) + smtpClient.EXPECT().Rcpt("bob@flyte.org").Return(nil).Times(1) + smtpClient.EXPECT().Data().Return(&stringWriter, nil).Times(1) + + smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) + + err := smtpEmailer.SendEmail(context.Background(), &admin.EmailMessage{ + SubjectLine: "subject", + SenderEmail: "flyte@flyte.org", + RecipientsEmail: []string{"alice@flyte.org", "bob@flyte.org"}, + Body: "This is an email.", + }) + + assert.True(t, strings.Contains(stringWriter.buffer, "From: sender")) + assert.True(t, strings.Contains(stringWriter.buffer, "To: alice@flyte.org,bob@flyte.org")) + assert.True(t, strings.Contains(stringWriter.buffer, "Subject: subject")) + assert.True(t, strings.Contains(stringWriter.buffer, "This is an email.")) + assert.Nil(t, err) + +} + +func TestSendMailCreateClientError(t *testing.T) { + auth := smtp.PlainAuth("", "user", "password", "localhost") + + tlsConf := tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + } + + smtpClient := notification_mocks.NewSMTPClient(t) + smtpClient.EXPECT().Noop().Return(errors.New("no connection")).Times(1) + smtpClient.EXPECT().Close().Return(nil).Times(1) + smtpClient.EXPECT().Hello("localhost").Return(errors.New("error hello")).Times(1) + + smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) + + err := smtpEmailer.SendEmail(context.Background(), &admin.EmailMessage{ + SubjectLine: "subject", + SenderEmail: "flyte@flyte.org", + RecipientsEmail: []string{"alice@flyte.org", "bob@flyte.org"}, + Body: "This is an email.", + }) + + assert.Equal(t, flyte_errors.NewFlyteAdminErrorf(codes.Internal, "errors were seen while sending emails"), err) + +} + +func TestSendMailErrorMail(t *testing.T) { + auth := smtp.PlainAuth("", "user", "password", "localhost") + tlsConf := tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + } + + smtpClient := notification_mocks.NewSMTPClient(t) + smtpClient.EXPECT().Noop().Return(errors.New("no connection")).Times(1) + smtpClient.EXPECT().Close().Return(nil).Times(1) + smtpClient.EXPECT().Hello("localhost").Return(nil).Times(1) + smtpClient.EXPECT().Extension("STARTTLS").Return(true, "").Times(1) + smtpClient.EXPECT().StartTLS(&tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + }).Return(nil).Times(1) + smtpClient.EXPECT().Extension("AUTH").Return(true, "").Times(1) + smtpClient.EXPECT().Auth(auth).Return(nil).Times(1) + smtpClient.EXPECT().Mail("flyte@flyte.org").Return(errors.New("error sending mail")).Times(1) + + smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) + + err := smtpEmailer.SendEmail(context.Background(), &admin.EmailMessage{ + SubjectLine: "subject", + SenderEmail: "flyte@flyte.org", + RecipientsEmail: []string{"alice@flyte.org", "bob@flyte.org"}, + Body: "This is an email.", + }) + + assert.Equal(t, flyte_errors.NewFlyteAdminErrorf(codes.Internal, "errors were seen while sending emails"), err) + +} + +func TestSendMailErrorRecipient(t *testing.T) { + auth := smtp.PlainAuth("", "user", "password", "localhost") + tlsConf := tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + } + + smtpClient := notification_mocks.NewSMTPClient(t) + smtpClient.EXPECT().Noop().Return(errors.New("no connection")).Times(1) + smtpClient.EXPECT().Close().Return(nil).Times(1) + smtpClient.EXPECT().Hello("localhost").Return(nil).Times(1) + smtpClient.EXPECT().Extension("STARTTLS").Return(true, "").Times(1) + smtpClient.EXPECT().StartTLS(&tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + }).Return(nil).Times(1) + smtpClient.EXPECT().Extension("AUTH").Return(true, "").Times(1) + smtpClient.EXPECT().Auth(auth).Return(nil).Times(1) + smtpClient.EXPECT().Mail("flyte@flyte.org").Return(nil).Times(1) + smtpClient.EXPECT().Rcpt("alice@flyte.org").Return(errors.New("error adding recipient")).Times(1) + + smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) + + err := smtpEmailer.SendEmail(context.Background(), &admin.EmailMessage{ + SubjectLine: "subject", + SenderEmail: "flyte@flyte.org", + RecipientsEmail: []string{"alice@flyte.org", "bob@flyte.org"}, + Body: "This is an email.", + }) + + assert.Equal(t, flyte_errors.NewFlyteAdminErrorf(codes.Internal, "errors were seen while sending emails"), err) + +} + +func TestSendMailErrorData(t *testing.T) { + auth := smtp.PlainAuth("", "user", "password", "localhost") + tlsConf := tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + } + + smtpClient := notification_mocks.NewSMTPClient(t) + smtpClient.EXPECT().Noop().Return(errors.New("no connection")).Times(1) + smtpClient.EXPECT().Close().Return(nil).Times(1) + smtpClient.EXPECT().Hello("localhost").Return(nil).Times(1) + smtpClient.EXPECT().Extension("STARTTLS").Return(true, "").Times(1) + smtpClient.EXPECT().StartTLS(&tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + }).Return(nil).Times(1) + smtpClient.EXPECT().Extension("AUTH").Return(true, "").Times(1) + smtpClient.EXPECT().Auth(auth).Return(nil).Times(1) + smtpClient.EXPECT().Mail("flyte@flyte.org").Return(nil).Times(1) + smtpClient.EXPECT().Rcpt("alice@flyte.org").Return(nil).Times(1) + smtpClient.EXPECT().Rcpt("bob@flyte.org").Return(nil).Times(1) + smtpClient.EXPECT().Data().Return(nil, errors.New("error creating data writer")).Times(1) + + smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) + + err := smtpEmailer.SendEmail(context.Background(), &admin.EmailMessage{ + SubjectLine: "subject", + SenderEmail: "flyte@flyte.org", + RecipientsEmail: []string{"alice@flyte.org", "bob@flyte.org"}, + Body: "This is an email.", + }) + + assert.Equal(t, flyte_errors.NewFlyteAdminErrorf(codes.Internal, "errors were seen while sending emails"), err) + +} + +func TestSendMailErrorWriting(t *testing.T) { + auth := smtp.PlainAuth("", "user", "password", "localhost") + + tlsConf := tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + } + + stringWriter := StringWriter{buffer: "", writeErr: errors.New("error writing"), closeErr: nil} + + smtpClient := notification_mocks.NewSMTPClient(t) + smtpClient.EXPECT().Noop().Return(errors.New("no connection")).Times(1) + smtpClient.EXPECT().Close().Return(nil).Times(1) + smtpClient.EXPECT().Hello("localhost").Return(nil).Times(1) + smtpClient.EXPECT().Extension("STARTTLS").Return(true, "").Times(1) + smtpClient.EXPECT().StartTLS(&tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + }).Return(nil).Times(1) + smtpClient.EXPECT().Extension("AUTH").Return(true, "").Times(1) + smtpClient.EXPECT().Auth(auth).Return(nil).Times(1) + smtpClient.EXPECT().Mail("flyte@flyte.org").Return(nil).Times(1) + smtpClient.EXPECT().Rcpt("alice@flyte.org").Return(nil).Times(1) + smtpClient.EXPECT().Rcpt("bob@flyte.org").Return(nil).Times(1) + smtpClient.EXPECT().Data().Return(&stringWriter, nil).Times(1) + + smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) + + err := smtpEmailer.SendEmail(context.Background(), &admin.EmailMessage{ + SubjectLine: "subject", + SenderEmail: "flyte@flyte.org", + RecipientsEmail: []string{"alice@flyte.org", "bob@flyte.org"}, + Body: "This is an email.", + }) + + assert.Equal(t, flyte_errors.NewFlyteAdminErrorf(codes.Internal, "errors were seen while sending emails"), err) + +} + +func TestSendMailErrorClose(t *testing.T) { + auth := smtp.PlainAuth("", "user", "password", "localhost") + + tlsConf := tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + } + + stringWriter := StringWriter{buffer: "", writeErr: nil, closeErr: errors.New("error writing")} + + smtpClient := notification_mocks.NewSMTPClient(t) + smtpClient.EXPECT().Noop().Return(errors.New("no connection")).Times(1) + smtpClient.EXPECT().Close().Return(nil).Times(1) + smtpClient.EXPECT().Hello("localhost").Return(nil).Times(1) + smtpClient.EXPECT().Extension("STARTTLS").Return(true, "").Times(1) + smtpClient.EXPECT().StartTLS(&tls.Config{ + InsecureSkipVerify: false, + ServerName: "localhost", + MinVersion: tls.VersionTLS13, + }).Return(nil).Times(1) + smtpClient.EXPECT().Extension("AUTH").Return(true, "").Times(1) + smtpClient.EXPECT().Auth(auth).Return(nil).Times(1) + smtpClient.EXPECT().Mail("flyte@flyte.org").Return(nil).Times(1) + smtpClient.EXPECT().Rcpt("alice@flyte.org").Return(nil).Times(1) + smtpClient.EXPECT().Rcpt("bob@flyte.org").Return(nil).Times(1) + smtpClient.EXPECT().Data().Return(&stringWriter, nil).Times(1) + + smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) + + err := smtpEmailer.SendEmail(context.Background(), &admin.EmailMessage{ + SubjectLine: "subject", + SenderEmail: "flyte@flyte.org", + RecipientsEmail: []string{"alice@flyte.org", "bob@flyte.org"}, + Body: "This is an email.", + }) + + assert.True(t, strings.Contains(stringWriter.buffer, "From: sender")) + assert.True(t, strings.Contains(stringWriter.buffer, "To: alice@flyte.org,bob@flyte.org")) + assert.True(t, strings.Contains(stringWriter.buffer, "Subject: subject")) + assert.True(t, strings.Contains(stringWriter.buffer, "This is an email.")) + assert.Equal(t, flyte_errors.NewFlyteAdminErrorf(codes.Internal, "errors were seen while sending emails"), err) + +} + +func createSMTPEmailer(smtpClient notification_interfaces.SMTPClient, tlsConf *tls.Config, auth *smtp.Auth, creationErr error) *SMTPEmailer { + secretManagerMock := mocks.SecretManager{} + secretManagerMock.On("Get", mock.Anything, "smtp_password").Return("password", nil) + + notificationsConfig := getNotificationsEmailerConfig() + + return &SMTPEmailer{ + config: ¬ificationsConfig.NotificationsEmailerConfig, + systemMetrics: newEmailMetrics(promutils.NewTestScope()), + tlsConf: tlsConf, + auth: auth, + CreateSMTPClientFunc: func(connectString string) (notification_interfaces.SMTPClient, error) { + return smtpClient, creationErr + }, + smtpClient: smtpClient, + } +} diff --git a/flyteadmin/pkg/async/notifications/interfaces/smtp_client.go b/flyteadmin/pkg/async/notifications/interfaces/smtp_client.go new file mode 100644 index 0000000000..9d22cdc345 --- /dev/null +++ b/flyteadmin/pkg/async/notifications/interfaces/smtp_client.go @@ -0,0 +1,22 @@ +package interfaces + +import ( + "crypto/tls" + "io" + "net/smtp" +) + +// This interface is introduced to allow for mocking of the smtp.Client object. + +//go:generate mockery --name=SMTPClient --output=../mocks --case=underscore --with-expecter +type SMTPClient interface { + Hello(localName string) error + Extension(ext string) (bool, string) + Auth(a smtp.Auth) error + StartTLS(config *tls.Config) error + Noop() error + Close() error + Mail(from string) error + Rcpt(to string) error + Data() (io.WriteCloser, error) +} diff --git a/flyteadmin/pkg/async/notifications/mocks/smtp_client.go b/flyteadmin/pkg/async/notifications/mocks/smtp_client.go new file mode 100644 index 0000000000..39c3a63caa --- /dev/null +++ b/flyteadmin/pkg/async/notifications/mocks/smtp_client.go @@ -0,0 +1,472 @@ +// Code generated by mockery v2.45.1. DO NOT EDIT. + +package mocks + +import ( + io "io" + smtp "net/smtp" + + mock "github.com/stretchr/testify/mock" + + tls "crypto/tls" +) + +// SMTPClient is an autogenerated mock type for the SMTPClient type +type SMTPClient struct { + mock.Mock +} + +type SMTPClient_Expecter struct { + mock *mock.Mock +} + +func (_m *SMTPClient) EXPECT() *SMTPClient_Expecter { + return &SMTPClient_Expecter{mock: &_m.Mock} +} + +// Auth provides a mock function with given fields: a +func (_m *SMTPClient) Auth(a smtp.Auth) error { + ret := _m.Called(a) + + if len(ret) == 0 { + panic("no return value specified for Auth") + } + + var r0 error + if rf, ok := ret.Get(0).(func(smtp.Auth) error); ok { + r0 = rf(a) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SMTPClient_Auth_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Auth' +type SMTPClient_Auth_Call struct { + *mock.Call +} + +// Auth is a helper method to define mock.On call +// - a smtp.Auth +func (_e *SMTPClient_Expecter) Auth(a interface{}) *SMTPClient_Auth_Call { + return &SMTPClient_Auth_Call{Call: _e.mock.On("Auth", a)} +} + +func (_c *SMTPClient_Auth_Call) Run(run func(a smtp.Auth)) *SMTPClient_Auth_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(smtp.Auth)) + }) + return _c +} + +func (_c *SMTPClient_Auth_Call) Return(_a0 error) *SMTPClient_Auth_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *SMTPClient_Auth_Call) RunAndReturn(run func(smtp.Auth) error) *SMTPClient_Auth_Call { + _c.Call.Return(run) + return _c +} + +// Close provides a mock function with given fields: +func (_m *SMTPClient) Close() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Close") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SMTPClient_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' +type SMTPClient_Close_Call struct { + *mock.Call +} + +// Close is a helper method to define mock.On call +func (_e *SMTPClient_Expecter) Close() *SMTPClient_Close_Call { + return &SMTPClient_Close_Call{Call: _e.mock.On("Close")} +} + +func (_c *SMTPClient_Close_Call) Run(run func()) *SMTPClient_Close_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *SMTPClient_Close_Call) Return(_a0 error) *SMTPClient_Close_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *SMTPClient_Close_Call) RunAndReturn(run func() error) *SMTPClient_Close_Call { + _c.Call.Return(run) + return _c +} + +// Data provides a mock function with given fields: +func (_m *SMTPClient) Data() (io.WriteCloser, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Data") + } + + var r0 io.WriteCloser + var r1 error + if rf, ok := ret.Get(0).(func() (io.WriteCloser, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() io.WriteCloser); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(io.WriteCloser) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SMTPClient_Data_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Data' +type SMTPClient_Data_Call struct { + *mock.Call +} + +// Data is a helper method to define mock.On call +func (_e *SMTPClient_Expecter) Data() *SMTPClient_Data_Call { + return &SMTPClient_Data_Call{Call: _e.mock.On("Data")} +} + +func (_c *SMTPClient_Data_Call) Run(run func()) *SMTPClient_Data_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *SMTPClient_Data_Call) Return(_a0 io.WriteCloser, _a1 error) *SMTPClient_Data_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *SMTPClient_Data_Call) RunAndReturn(run func() (io.WriteCloser, error)) *SMTPClient_Data_Call { + _c.Call.Return(run) + return _c +} + +// Extension provides a mock function with given fields: ext +func (_m *SMTPClient) Extension(ext string) (bool, string) { + ret := _m.Called(ext) + + if len(ret) == 0 { + panic("no return value specified for Extension") + } + + var r0 bool + var r1 string + if rf, ok := ret.Get(0).(func(string) (bool, string)); ok { + return rf(ext) + } + if rf, ok := ret.Get(0).(func(string) bool); ok { + r0 = rf(ext) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(string) string); ok { + r1 = rf(ext) + } else { + r1 = ret.Get(1).(string) + } + + return r0, r1 +} + +// SMTPClient_Extension_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Extension' +type SMTPClient_Extension_Call struct { + *mock.Call +} + +// Extension is a helper method to define mock.On call +// - ext string +func (_e *SMTPClient_Expecter) Extension(ext interface{}) *SMTPClient_Extension_Call { + return &SMTPClient_Extension_Call{Call: _e.mock.On("Extension", ext)} +} + +func (_c *SMTPClient_Extension_Call) Run(run func(ext string)) *SMTPClient_Extension_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *SMTPClient_Extension_Call) Return(_a0 bool, _a1 string) *SMTPClient_Extension_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *SMTPClient_Extension_Call) RunAndReturn(run func(string) (bool, string)) *SMTPClient_Extension_Call { + _c.Call.Return(run) + return _c +} + +// Hello provides a mock function with given fields: localName +func (_m *SMTPClient) Hello(localName string) error { + ret := _m.Called(localName) + + if len(ret) == 0 { + panic("no return value specified for Hello") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(localName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SMTPClient_Hello_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Hello' +type SMTPClient_Hello_Call struct { + *mock.Call +} + +// Hello is a helper method to define mock.On call +// - localName string +func (_e *SMTPClient_Expecter) Hello(localName interface{}) *SMTPClient_Hello_Call { + return &SMTPClient_Hello_Call{Call: _e.mock.On("Hello", localName)} +} + +func (_c *SMTPClient_Hello_Call) Run(run func(localName string)) *SMTPClient_Hello_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *SMTPClient_Hello_Call) Return(_a0 error) *SMTPClient_Hello_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *SMTPClient_Hello_Call) RunAndReturn(run func(string) error) *SMTPClient_Hello_Call { + _c.Call.Return(run) + return _c +} + +// Mail provides a mock function with given fields: from +func (_m *SMTPClient) Mail(from string) error { + ret := _m.Called(from) + + if len(ret) == 0 { + panic("no return value specified for Mail") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(from) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SMTPClient_Mail_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Mail' +type SMTPClient_Mail_Call struct { + *mock.Call +} + +// Mail is a helper method to define mock.On call +// - from string +func (_e *SMTPClient_Expecter) Mail(from interface{}) *SMTPClient_Mail_Call { + return &SMTPClient_Mail_Call{Call: _e.mock.On("Mail", from)} +} + +func (_c *SMTPClient_Mail_Call) Run(run func(from string)) *SMTPClient_Mail_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *SMTPClient_Mail_Call) Return(_a0 error) *SMTPClient_Mail_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *SMTPClient_Mail_Call) RunAndReturn(run func(string) error) *SMTPClient_Mail_Call { + _c.Call.Return(run) + return _c +} + +// Noop provides a mock function with given fields: +func (_m *SMTPClient) Noop() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Noop") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SMTPClient_Noop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Noop' +type SMTPClient_Noop_Call struct { + *mock.Call +} + +// Noop is a helper method to define mock.On call +func (_e *SMTPClient_Expecter) Noop() *SMTPClient_Noop_Call { + return &SMTPClient_Noop_Call{Call: _e.mock.On("Noop")} +} + +func (_c *SMTPClient_Noop_Call) Run(run func()) *SMTPClient_Noop_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *SMTPClient_Noop_Call) Return(_a0 error) *SMTPClient_Noop_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *SMTPClient_Noop_Call) RunAndReturn(run func() error) *SMTPClient_Noop_Call { + _c.Call.Return(run) + return _c +} + +// Rcpt provides a mock function with given fields: to +func (_m *SMTPClient) Rcpt(to string) error { + ret := _m.Called(to) + + if len(ret) == 0 { + panic("no return value specified for Rcpt") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(to) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SMTPClient_Rcpt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Rcpt' +type SMTPClient_Rcpt_Call struct { + *mock.Call +} + +// Rcpt is a helper method to define mock.On call +// - to string +func (_e *SMTPClient_Expecter) Rcpt(to interface{}) *SMTPClient_Rcpt_Call { + return &SMTPClient_Rcpt_Call{Call: _e.mock.On("Rcpt", to)} +} + +func (_c *SMTPClient_Rcpt_Call) Run(run func(to string)) *SMTPClient_Rcpt_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *SMTPClient_Rcpt_Call) Return(_a0 error) *SMTPClient_Rcpt_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *SMTPClient_Rcpt_Call) RunAndReturn(run func(string) error) *SMTPClient_Rcpt_Call { + _c.Call.Return(run) + return _c +} + +// StartTLS provides a mock function with given fields: config +func (_m *SMTPClient) StartTLS(config *tls.Config) error { + ret := _m.Called(config) + + if len(ret) == 0 { + panic("no return value specified for StartTLS") + } + + var r0 error + if rf, ok := ret.Get(0).(func(*tls.Config) error); ok { + r0 = rf(config) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SMTPClient_StartTLS_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StartTLS' +type SMTPClient_StartTLS_Call struct { + *mock.Call +} + +// StartTLS is a helper method to define mock.On call +// - config *tls.Config +func (_e *SMTPClient_Expecter) StartTLS(config interface{}) *SMTPClient_StartTLS_Call { + return &SMTPClient_StartTLS_Call{Call: _e.mock.On("StartTLS", config)} +} + +func (_c *SMTPClient_StartTLS_Call) Run(run func(config *tls.Config)) *SMTPClient_StartTLS_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*tls.Config)) + }) + return _c +} + +func (_c *SMTPClient_StartTLS_Call) Return(_a0 error) *SMTPClient_StartTLS_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *SMTPClient_StartTLS_Call) RunAndReturn(run func(*tls.Config) error) *SMTPClient_StartTLS_Call { + _c.Call.Return(run) + return _c +} + +// NewSMTPClient creates a new instance of SMTPClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewSMTPClient(t interface { + mock.TestingT + Cleanup(func()) +}) *SMTPClient { + mock := &SMTPClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} From 8be8c466b398c86b51e542f967c159f8f743ebee Mon Sep 17 00:00:00 2001 From: Ulbrich Robert Date: Fri, 13 Sep 2024 16:30:59 +0200 Subject: [PATCH 26/28] Applying gci Signed-off-by: Ulbrich Robert --- .../async/notifications/implementations/smtp_emailer_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go index 924bd1278c..aaf8be6574 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go @@ -4,19 +4,17 @@ import ( "context" "crypto/tls" "errors" - "google.golang.org/grpc/codes" "net/smtp" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "google.golang.org/grpc/codes" notification_interfaces "github.com/flyteorg/flyte/flyteadmin/pkg/async/notifications/interfaces" notification_mocks "github.com/flyteorg/flyte/flyteadmin/pkg/async/notifications/mocks" - flyte_errors "github.com/flyteorg/flyte/flyteadmin/pkg/errors" - "github.com/flyteorg/flyte/flyteadmin/pkg/runtime/interfaces" "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/admin" "github.com/flyteorg/flyte/flyteplugins/go/tasks/pluginmachinery/core/mocks" From 64f3333082dccba827c374b25a8f6c6e2368b8f0 Mon Sep 17 00:00:00 2001 From: Ulbrich Robert Date: Mon, 16 Sep 2024 07:28:36 +0200 Subject: [PATCH 27/28] Faking mockery generation Signed-off-by: Ulbrich Robert --- .../notifications/implementations/smtp_emailer_test.go | 6 +++++- .../pkg/async/notifications/interfaces/smtp_client.go | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go index aaf8be6574..8af08f84e0 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go @@ -68,7 +68,11 @@ func TestEmailCreation(t *testing.T) { } body := createMailBody("sender@sender.com", &email) - assert.Equal(t, "From: sender@sender.com\r\nTo: john@doe.com,teresa@tester.com\r\nSubject: subject\r\nContent-Type: text/html; charset=\"UTF-8\"\r\n\r\nEmail Body", body) + assert.Contains(t, body, "From: sender@sender.com\r\n") + assert.Contains(t, body, "To: john@doe.com,teresa@tester.com") + assert.Contains(t, body, "Subject: subject\r\n") + assert.Contains(t, body, "Content-Type: text/html; charset=\"UTF-8\"\r\n") + assert.Contains(t, body, "Email Body") } func TestNewSmtpEmailer(t *testing.T) { diff --git a/flyteadmin/pkg/async/notifications/interfaces/smtp_client.go b/flyteadmin/pkg/async/notifications/interfaces/smtp_client.go index 9d22cdc345..bdc6171f46 100644 --- a/flyteadmin/pkg/async/notifications/interfaces/smtp_client.go +++ b/flyteadmin/pkg/async/notifications/interfaces/smtp_client.go @@ -8,7 +8,7 @@ import ( // This interface is introduced to allow for mocking of the smtp.Client object. -//go:generate mockery --name=SMTPClient --output=../mocks --case=underscore --with-expecter +//go:generate mockery -name=SMTPClient -output=../mocks -case=underscore type SMTPClient interface { Hello(localName string) error Extension(ext string) (bool, string) From 50ce3209117701b8f3afa9f85ae8b54dd9fdd84c Mon Sep 17 00:00:00 2001 From: Ulbrich Robert Date: Mon, 16 Sep 2024 09:34:07 +0200 Subject: [PATCH 28/28] Using mocker 1.0.1 Signed-off-by: Ulbrich Robert --- .../implementations/smtp_emailer_test.go | 182 +++++----- .../async/notifications/mocks/smtp_client.go | 329 +++++------------- 2 files changed, 180 insertions(+), 331 deletions(-) diff --git a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go index 8af08f84e0..558a5d6408 100644 --- a/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go +++ b/flyteadmin/pkg/async/notifications/implementations/smtp_emailer_test.go @@ -95,16 +95,16 @@ func TestCreateClient(t *testing.T) { MinVersion: tls.VersionTLS13, } - smtpClient := notification_mocks.NewSMTPClient(t) - smtpClient.EXPECT().Hello("localhost").Return(nil).Times(1) - smtpClient.EXPECT().Extension("STARTTLS").Return(true, "").Times(1) - smtpClient.EXPECT().StartTLS(&tls.Config{ + smtpClient := ¬ification_mocks.SMTPClient{} + smtpClient.On("Hello", "localhost").Return(nil) + smtpClient.On("Extension", "STARTTLS").Return(true, "") + smtpClient.On("StartTLS", &tls.Config{ InsecureSkipVerify: false, ServerName: "localhost", MinVersion: tls.VersionTLS13, - }).Return(nil).Times(1) - smtpClient.EXPECT().Extension("AUTH").Return(true, "").Times(1) - smtpClient.EXPECT().Auth(auth).Return(nil).Times(1) + }).Return(nil) + smtpClient.On("Extension", "AUTH").Return(true, "") + smtpClient.On("Auth", auth).Return(nil) smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) @@ -124,7 +124,7 @@ func TestCreateClientErrorCreatingClient(t *testing.T) { MinVersion: tls.VersionTLS13, } - smtpClient := notification_mocks.NewSMTPClient(t) + smtpClient := ¬ification_mocks.SMTPClient{} smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, errors.New("error creating client")) @@ -144,8 +144,8 @@ func TestCreateClientErrorHello(t *testing.T) { MinVersion: tls.VersionTLS13, } - smtpClient := notification_mocks.NewSMTPClient(t) - smtpClient.EXPECT().Hello("localhost").Return(errors.New("Error with hello")).Times(1) + smtpClient := ¬ification_mocks.SMTPClient{} + smtpClient.On("Hello", "localhost").Return(errors.New("Error with hello")) smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) @@ -165,10 +165,10 @@ func TestCreateClientErrorStartTLS(t *testing.T) { MinVersion: tls.VersionTLS13, } - smtpClient := notification_mocks.NewSMTPClient(t) - smtpClient.EXPECT().Hello("localhost").Return(nil).Times(1) - smtpClient.EXPECT().Extension("STARTTLS").Return(true, "").Times(1) - smtpClient.EXPECT().StartTLS(&tls.Config{ + smtpClient := ¬ification_mocks.SMTPClient{} + smtpClient.On("Hello", "localhost").Return(nil).Times(1) + smtpClient.On("Extension", "STARTTLS").Return(true, "").Times(1) + smtpClient.On("StartTLS", &tls.Config{ InsecureSkipVerify: false, ServerName: "localhost", MinVersion: tls.VersionTLS13, @@ -192,16 +192,16 @@ func TestCreateClientErrorAuth(t *testing.T) { MinVersion: tls.VersionTLS13, } - smtpClient := notification_mocks.NewSMTPClient(t) - smtpClient.EXPECT().Hello("localhost").Return(nil).Times(1) - smtpClient.EXPECT().Extension("STARTTLS").Return(true, "").Times(1) - smtpClient.EXPECT().StartTLS(&tls.Config{ + smtpClient := ¬ification_mocks.SMTPClient{} + smtpClient.On("Hello", "localhost").Return(nil).Times(1) + smtpClient.On("Extension", "STARTTLS").Return(true, "").Times(1) + smtpClient.On("StartTLS", &tls.Config{ InsecureSkipVerify: false, ServerName: "localhost", MinVersion: tls.VersionTLS13, }).Return(nil).Times(1) - smtpClient.EXPECT().Extension("AUTH").Return(true, "").Times(1) - smtpClient.EXPECT().Auth(auth).Return(errors.New("Error with hello")).Times(1) + smtpClient.On("Extension", "AUTH").Return(true, "").Times(1) + smtpClient.On("Auth", auth).Return(errors.New("Error with hello")).Times(1) smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) @@ -223,22 +223,22 @@ func TestSendMail(t *testing.T) { stringWriter := StringWriter{buffer: ""} - smtpClient := notification_mocks.NewSMTPClient(t) - smtpClient.EXPECT().Noop().Return(errors.New("no connection")).Times(1) - smtpClient.EXPECT().Close().Return(nil).Times(1) - smtpClient.EXPECT().Hello("localhost").Return(nil).Times(1) - smtpClient.EXPECT().Extension("STARTTLS").Return(true, "").Times(1) - smtpClient.EXPECT().StartTLS(&tls.Config{ + smtpClient := ¬ification_mocks.SMTPClient{} + smtpClient.On("Noop").Return(errors.New("no connection")).Times(1) + smtpClient.On("Close").Return(nil).Times(1) + smtpClient.On("Hello", "localhost").Return(nil).Times(1) + smtpClient.On("Extension", "STARTTLS").Return(true, "").Times(1) + smtpClient.On("StartTLS", &tls.Config{ InsecureSkipVerify: false, ServerName: "localhost", MinVersion: tls.VersionTLS13, }).Return(nil).Times(1) - smtpClient.EXPECT().Extension("AUTH").Return(true, "").Times(1) - smtpClient.EXPECT().Auth(auth).Return(nil).Times(1) - smtpClient.EXPECT().Mail("flyte@flyte.org").Return(nil).Times(1) - smtpClient.EXPECT().Rcpt("alice@flyte.org").Return(nil).Times(1) - smtpClient.EXPECT().Rcpt("bob@flyte.org").Return(nil).Times(1) - smtpClient.EXPECT().Data().Return(&stringWriter, nil).Times(1) + smtpClient.On("Extension", "AUTH").Return(true, "").Times(1) + smtpClient.On("Auth", auth).Return(nil).Times(1) + smtpClient.On("Mail", "flyte@flyte.org").Return(nil).Times(1) + smtpClient.On("Rcpt", "alice@flyte.org").Return(nil).Times(1) + smtpClient.On("Rcpt", "bob@flyte.org").Return(nil).Times(1) + smtpClient.On("Data").Return(&stringWriter, nil).Times(1) smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) @@ -266,10 +266,10 @@ func TestSendMailCreateClientError(t *testing.T) { MinVersion: tls.VersionTLS13, } - smtpClient := notification_mocks.NewSMTPClient(t) - smtpClient.EXPECT().Noop().Return(errors.New("no connection")).Times(1) - smtpClient.EXPECT().Close().Return(nil).Times(1) - smtpClient.EXPECT().Hello("localhost").Return(errors.New("error hello")).Times(1) + smtpClient := ¬ification_mocks.SMTPClient{} + smtpClient.On("Noop").Return(errors.New("no connection")).Times(1) + smtpClient.On("Close").Return(nil).Times(1) + smtpClient.On("Hello", "localhost").Return(errors.New("error hello")).Times(1) smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) @@ -292,19 +292,19 @@ func TestSendMailErrorMail(t *testing.T) { MinVersion: tls.VersionTLS13, } - smtpClient := notification_mocks.NewSMTPClient(t) - smtpClient.EXPECT().Noop().Return(errors.New("no connection")).Times(1) - smtpClient.EXPECT().Close().Return(nil).Times(1) - smtpClient.EXPECT().Hello("localhost").Return(nil).Times(1) - smtpClient.EXPECT().Extension("STARTTLS").Return(true, "").Times(1) - smtpClient.EXPECT().StartTLS(&tls.Config{ + smtpClient := ¬ification_mocks.SMTPClient{} + smtpClient.On("Noop").Return(errors.New("no connection")).Times(1) + smtpClient.On("Close").Return(nil).Times(1) + smtpClient.On("Hello", "localhost").Return(nil).Times(1) + smtpClient.On("Extension", "STARTTLS").Return(true, "").Times(1) + smtpClient.On("StartTLS", &tls.Config{ InsecureSkipVerify: false, ServerName: "localhost", MinVersion: tls.VersionTLS13, }).Return(nil).Times(1) - smtpClient.EXPECT().Extension("AUTH").Return(true, "").Times(1) - smtpClient.EXPECT().Auth(auth).Return(nil).Times(1) - smtpClient.EXPECT().Mail("flyte@flyte.org").Return(errors.New("error sending mail")).Times(1) + smtpClient.On("Extension", "AUTH").Return(true, "").Times(1) + smtpClient.On("Auth", auth).Return(nil).Times(1) + smtpClient.On("Mail", "flyte@flyte.org").Return(errors.New("error sending mail")).Times(1) smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) @@ -327,20 +327,20 @@ func TestSendMailErrorRecipient(t *testing.T) { MinVersion: tls.VersionTLS13, } - smtpClient := notification_mocks.NewSMTPClient(t) - smtpClient.EXPECT().Noop().Return(errors.New("no connection")).Times(1) - smtpClient.EXPECT().Close().Return(nil).Times(1) - smtpClient.EXPECT().Hello("localhost").Return(nil).Times(1) - smtpClient.EXPECT().Extension("STARTTLS").Return(true, "").Times(1) - smtpClient.EXPECT().StartTLS(&tls.Config{ + smtpClient := ¬ification_mocks.SMTPClient{} + smtpClient.On("Noop").Return(errors.New("no connection")).Times(1) + smtpClient.On("Close").Return(nil).Times(1) + smtpClient.On("Hello", "localhost").Return(nil).Times(1) + smtpClient.On("Extension", "STARTTLS").Return(true, "").Times(1) + smtpClient.On("StartTLS", &tls.Config{ InsecureSkipVerify: false, ServerName: "localhost", MinVersion: tls.VersionTLS13, }).Return(nil).Times(1) - smtpClient.EXPECT().Extension("AUTH").Return(true, "").Times(1) - smtpClient.EXPECT().Auth(auth).Return(nil).Times(1) - smtpClient.EXPECT().Mail("flyte@flyte.org").Return(nil).Times(1) - smtpClient.EXPECT().Rcpt("alice@flyte.org").Return(errors.New("error adding recipient")).Times(1) + smtpClient.On("Extension", "AUTH").Return(true, "").Times(1) + smtpClient.On("Auth", auth).Return(nil).Times(1) + smtpClient.On("Mail", "flyte@flyte.org").Return(nil).Times(1) + smtpClient.On("Rcpt", "alice@flyte.org").Return(errors.New("error adding recipient")).Times(1) smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) @@ -363,22 +363,22 @@ func TestSendMailErrorData(t *testing.T) { MinVersion: tls.VersionTLS13, } - smtpClient := notification_mocks.NewSMTPClient(t) - smtpClient.EXPECT().Noop().Return(errors.New("no connection")).Times(1) - smtpClient.EXPECT().Close().Return(nil).Times(1) - smtpClient.EXPECT().Hello("localhost").Return(nil).Times(1) - smtpClient.EXPECT().Extension("STARTTLS").Return(true, "").Times(1) - smtpClient.EXPECT().StartTLS(&tls.Config{ + smtpClient := ¬ification_mocks.SMTPClient{} + smtpClient.On("Noop").Return(errors.New("no connection")).Times(1) + smtpClient.On("Close").Return(nil).Times(1) + smtpClient.On("Hello", "localhost").Return(nil).Times(1) + smtpClient.On("Extension", "STARTTLS").Return(true, "").Times(1) + smtpClient.On("StartTLS", &tls.Config{ InsecureSkipVerify: false, ServerName: "localhost", MinVersion: tls.VersionTLS13, }).Return(nil).Times(1) - smtpClient.EXPECT().Extension("AUTH").Return(true, "").Times(1) - smtpClient.EXPECT().Auth(auth).Return(nil).Times(1) - smtpClient.EXPECT().Mail("flyte@flyte.org").Return(nil).Times(1) - smtpClient.EXPECT().Rcpt("alice@flyte.org").Return(nil).Times(1) - smtpClient.EXPECT().Rcpt("bob@flyte.org").Return(nil).Times(1) - smtpClient.EXPECT().Data().Return(nil, errors.New("error creating data writer")).Times(1) + smtpClient.On("Extension", "AUTH").Return(true, "").Times(1) + smtpClient.On("Auth", auth).Return(nil).Times(1) + smtpClient.On("Mail", "flyte@flyte.org").Return(nil).Times(1) + smtpClient.On("Rcpt", "alice@flyte.org").Return(nil).Times(1) + smtpClient.On("Rcpt", "bob@flyte.org").Return(nil).Times(1) + smtpClient.On("Data").Return(nil, errors.New("error creating data writer")).Times(1) smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) @@ -404,22 +404,22 @@ func TestSendMailErrorWriting(t *testing.T) { stringWriter := StringWriter{buffer: "", writeErr: errors.New("error writing"), closeErr: nil} - smtpClient := notification_mocks.NewSMTPClient(t) - smtpClient.EXPECT().Noop().Return(errors.New("no connection")).Times(1) - smtpClient.EXPECT().Close().Return(nil).Times(1) - smtpClient.EXPECT().Hello("localhost").Return(nil).Times(1) - smtpClient.EXPECT().Extension("STARTTLS").Return(true, "").Times(1) - smtpClient.EXPECT().StartTLS(&tls.Config{ + smtpClient := ¬ification_mocks.SMTPClient{} + smtpClient.On("Noop").Return(errors.New("no connection")).Times(1) + smtpClient.On("Close").Return(nil).Times(1) + smtpClient.On("Hello", "localhost").Return(nil).Times(1) + smtpClient.On("Extension", "STARTTLS").Return(true, "").Times(1) + smtpClient.On("StartTLS", &tls.Config{ InsecureSkipVerify: false, ServerName: "localhost", MinVersion: tls.VersionTLS13, }).Return(nil).Times(1) - smtpClient.EXPECT().Extension("AUTH").Return(true, "").Times(1) - smtpClient.EXPECT().Auth(auth).Return(nil).Times(1) - smtpClient.EXPECT().Mail("flyte@flyte.org").Return(nil).Times(1) - smtpClient.EXPECT().Rcpt("alice@flyte.org").Return(nil).Times(1) - smtpClient.EXPECT().Rcpt("bob@flyte.org").Return(nil).Times(1) - smtpClient.EXPECT().Data().Return(&stringWriter, nil).Times(1) + smtpClient.On("Extension", "AUTH").Return(true, "").Times(1) + smtpClient.On("Auth", auth).Return(nil).Times(1) + smtpClient.On("Mail", "flyte@flyte.org").Return(nil).Times(1) + smtpClient.On("Rcpt", "alice@flyte.org").Return(nil).Times(1) + smtpClient.On("Rcpt", "bob@flyte.org").Return(nil).Times(1) + smtpClient.On("Data").Return(&stringWriter, nil).Times(1) smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) @@ -445,22 +445,22 @@ func TestSendMailErrorClose(t *testing.T) { stringWriter := StringWriter{buffer: "", writeErr: nil, closeErr: errors.New("error writing")} - smtpClient := notification_mocks.NewSMTPClient(t) - smtpClient.EXPECT().Noop().Return(errors.New("no connection")).Times(1) - smtpClient.EXPECT().Close().Return(nil).Times(1) - smtpClient.EXPECT().Hello("localhost").Return(nil).Times(1) - smtpClient.EXPECT().Extension("STARTTLS").Return(true, "").Times(1) - smtpClient.EXPECT().StartTLS(&tls.Config{ + smtpClient := ¬ification_mocks.SMTPClient{} + smtpClient.On("Noop").Return(errors.New("no connection")).Times(1) + smtpClient.On("Close").Return(nil).Times(1) + smtpClient.On("Hello", "localhost").Return(nil).Times(1) + smtpClient.On("Extension", "STARTTLS").Return(true, "").Times(1) + smtpClient.On("StartTLS", &tls.Config{ InsecureSkipVerify: false, ServerName: "localhost", MinVersion: tls.VersionTLS13, }).Return(nil).Times(1) - smtpClient.EXPECT().Extension("AUTH").Return(true, "").Times(1) - smtpClient.EXPECT().Auth(auth).Return(nil).Times(1) - smtpClient.EXPECT().Mail("flyte@flyte.org").Return(nil).Times(1) - smtpClient.EXPECT().Rcpt("alice@flyte.org").Return(nil).Times(1) - smtpClient.EXPECT().Rcpt("bob@flyte.org").Return(nil).Times(1) - smtpClient.EXPECT().Data().Return(&stringWriter, nil).Times(1) + smtpClient.On("Extension", "AUTH").Return(true, "").Times(1) + smtpClient.On("Auth", auth).Return(nil).Times(1) + smtpClient.On("Mail", "flyte@flyte.org").Return(nil).Times(1) + smtpClient.On("Rcpt", "alice@flyte.org").Return(nil).Times(1) + smtpClient.On("Rcpt", "bob@flyte.org").Return(nil).Times(1) + smtpClient.On("Data").Return(&stringWriter, nil).Times(1) smtpEmailer := createSMTPEmailer(smtpClient, &tlsConf, &auth, nil) diff --git a/flyteadmin/pkg/async/notifications/mocks/smtp_client.go b/flyteadmin/pkg/async/notifications/mocks/smtp_client.go index 39c3a63caa..11dafefc9c 100644 --- a/flyteadmin/pkg/async/notifications/mocks/smtp_client.go +++ b/flyteadmin/pkg/async/notifications/mocks/smtp_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.1. DO NOT EDIT. +// Code generated by mockery v1.0.1. DO NOT EDIT. package mocks @@ -16,22 +16,28 @@ type SMTPClient struct { mock.Mock } -type SMTPClient_Expecter struct { - mock *mock.Mock +type SMTPClient_Auth struct { + *mock.Call +} + +func (_m SMTPClient_Auth) Return(_a0 error) *SMTPClient_Auth { + return &SMTPClient_Auth{Call: _m.Call.Return(_a0)} +} + +func (_m *SMTPClient) OnAuth(a smtp.Auth) *SMTPClient_Auth { + c_call := _m.On("Auth", a) + return &SMTPClient_Auth{Call: c_call} } -func (_m *SMTPClient) EXPECT() *SMTPClient_Expecter { - return &SMTPClient_Expecter{mock: &_m.Mock} +func (_m *SMTPClient) OnAuthMatch(matchers ...interface{}) *SMTPClient_Auth { + c_call := _m.On("Auth", matchers...) + return &SMTPClient_Auth{Call: c_call} } // Auth provides a mock function with given fields: a func (_m *SMTPClient) Auth(a smtp.Auth) error { ret := _m.Called(a) - if len(ret) == 0 { - panic("no return value specified for Auth") - } - var r0 error if rf, ok := ret.Get(0).(func(smtp.Auth) error); ok { r0 = rf(a) @@ -42,42 +48,28 @@ func (_m *SMTPClient) Auth(a smtp.Auth) error { return r0 } -// SMTPClient_Auth_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Auth' -type SMTPClient_Auth_Call struct { +type SMTPClient_Close struct { *mock.Call } -// Auth is a helper method to define mock.On call -// - a smtp.Auth -func (_e *SMTPClient_Expecter) Auth(a interface{}) *SMTPClient_Auth_Call { - return &SMTPClient_Auth_Call{Call: _e.mock.On("Auth", a)} +func (_m SMTPClient_Close) Return(_a0 error) *SMTPClient_Close { + return &SMTPClient_Close{Call: _m.Call.Return(_a0)} } -func (_c *SMTPClient_Auth_Call) Run(run func(a smtp.Auth)) *SMTPClient_Auth_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(smtp.Auth)) - }) - return _c +func (_m *SMTPClient) OnClose() *SMTPClient_Close { + c_call := _m.On("Close") + return &SMTPClient_Close{Call: c_call} } -func (_c *SMTPClient_Auth_Call) Return(_a0 error) *SMTPClient_Auth_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *SMTPClient_Auth_Call) RunAndReturn(run func(smtp.Auth) error) *SMTPClient_Auth_Call { - _c.Call.Return(run) - return _c +func (_m *SMTPClient) OnCloseMatch(matchers ...interface{}) *SMTPClient_Close { + c_call := _m.On("Close", matchers...) + return &SMTPClient_Close{Call: c_call} } // Close provides a mock function with given fields: func (_m *SMTPClient) Close() error { ret := _m.Called() - if len(ret) == 0 { - panic("no return value specified for Close") - } - var r0 error if rf, ok := ret.Get(0).(func() error); ok { r0 = rf() @@ -88,46 +80,29 @@ func (_m *SMTPClient) Close() error { return r0 } -// SMTPClient_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' -type SMTPClient_Close_Call struct { +type SMTPClient_Data struct { *mock.Call } -// Close is a helper method to define mock.On call -func (_e *SMTPClient_Expecter) Close() *SMTPClient_Close_Call { - return &SMTPClient_Close_Call{Call: _e.mock.On("Close")} +func (_m SMTPClient_Data) Return(_a0 io.WriteCloser, _a1 error) *SMTPClient_Data { + return &SMTPClient_Data{Call: _m.Call.Return(_a0, _a1)} } -func (_c *SMTPClient_Close_Call) Run(run func()) *SMTPClient_Close_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c +func (_m *SMTPClient) OnData() *SMTPClient_Data { + c_call := _m.On("Data") + return &SMTPClient_Data{Call: c_call} } -func (_c *SMTPClient_Close_Call) Return(_a0 error) *SMTPClient_Close_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *SMTPClient_Close_Call) RunAndReturn(run func() error) *SMTPClient_Close_Call { - _c.Call.Return(run) - return _c +func (_m *SMTPClient) OnDataMatch(matchers ...interface{}) *SMTPClient_Data { + c_call := _m.On("Data", matchers...) + return &SMTPClient_Data{Call: c_call} } // Data provides a mock function with given fields: func (_m *SMTPClient) Data() (io.WriteCloser, error) { ret := _m.Called() - if len(ret) == 0 { - panic("no return value specified for Data") - } - var r0 io.WriteCloser - var r1 error - if rf, ok := ret.Get(0).(func() (io.WriteCloser, error)); ok { - return rf() - } if rf, ok := ret.Get(0).(func() io.WriteCloser); ok { r0 = rf() } else { @@ -136,6 +111,7 @@ func (_m *SMTPClient) Data() (io.WriteCloser, error) { } } + var r1 error if rf, ok := ret.Get(1).(func() error); ok { r1 = rf() } else { @@ -145,52 +121,36 @@ func (_m *SMTPClient) Data() (io.WriteCloser, error) { return r0, r1 } -// SMTPClient_Data_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Data' -type SMTPClient_Data_Call struct { +type SMTPClient_Extension struct { *mock.Call } -// Data is a helper method to define mock.On call -func (_e *SMTPClient_Expecter) Data() *SMTPClient_Data_Call { - return &SMTPClient_Data_Call{Call: _e.mock.On("Data")} -} - -func (_c *SMTPClient_Data_Call) Run(run func()) *SMTPClient_Data_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c +func (_m SMTPClient_Extension) Return(_a0 bool, _a1 string) *SMTPClient_Extension { + return &SMTPClient_Extension{Call: _m.Call.Return(_a0, _a1)} } -func (_c *SMTPClient_Data_Call) Return(_a0 io.WriteCloser, _a1 error) *SMTPClient_Data_Call { - _c.Call.Return(_a0, _a1) - return _c +func (_m *SMTPClient) OnExtension(ext string) *SMTPClient_Extension { + c_call := _m.On("Extension", ext) + return &SMTPClient_Extension{Call: c_call} } -func (_c *SMTPClient_Data_Call) RunAndReturn(run func() (io.WriteCloser, error)) *SMTPClient_Data_Call { - _c.Call.Return(run) - return _c +func (_m *SMTPClient) OnExtensionMatch(matchers ...interface{}) *SMTPClient_Extension { + c_call := _m.On("Extension", matchers...) + return &SMTPClient_Extension{Call: c_call} } // Extension provides a mock function with given fields: ext func (_m *SMTPClient) Extension(ext string) (bool, string) { ret := _m.Called(ext) - if len(ret) == 0 { - panic("no return value specified for Extension") - } - var r0 bool - var r1 string - if rf, ok := ret.Get(0).(func(string) (bool, string)); ok { - return rf(ext) - } if rf, ok := ret.Get(0).(func(string) bool); ok { r0 = rf(ext) } else { r0 = ret.Get(0).(bool) } + var r1 string if rf, ok := ret.Get(1).(func(string) string); ok { r1 = rf(ext) } else { @@ -200,42 +160,28 @@ func (_m *SMTPClient) Extension(ext string) (bool, string) { return r0, r1 } -// SMTPClient_Extension_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Extension' -type SMTPClient_Extension_Call struct { +type SMTPClient_Hello struct { *mock.Call } -// Extension is a helper method to define mock.On call -// - ext string -func (_e *SMTPClient_Expecter) Extension(ext interface{}) *SMTPClient_Extension_Call { - return &SMTPClient_Extension_Call{Call: _e.mock.On("Extension", ext)} -} - -func (_c *SMTPClient_Extension_Call) Run(run func(ext string)) *SMTPClient_Extension_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) - }) - return _c +func (_m SMTPClient_Hello) Return(_a0 error) *SMTPClient_Hello { + return &SMTPClient_Hello{Call: _m.Call.Return(_a0)} } -func (_c *SMTPClient_Extension_Call) Return(_a0 bool, _a1 string) *SMTPClient_Extension_Call { - _c.Call.Return(_a0, _a1) - return _c +func (_m *SMTPClient) OnHello(localName string) *SMTPClient_Hello { + c_call := _m.On("Hello", localName) + return &SMTPClient_Hello{Call: c_call} } -func (_c *SMTPClient_Extension_Call) RunAndReturn(run func(string) (bool, string)) *SMTPClient_Extension_Call { - _c.Call.Return(run) - return _c +func (_m *SMTPClient) OnHelloMatch(matchers ...interface{}) *SMTPClient_Hello { + c_call := _m.On("Hello", matchers...) + return &SMTPClient_Hello{Call: c_call} } // Hello provides a mock function with given fields: localName func (_m *SMTPClient) Hello(localName string) error { ret := _m.Called(localName) - if len(ret) == 0 { - panic("no return value specified for Hello") - } - var r0 error if rf, ok := ret.Get(0).(func(string) error); ok { r0 = rf(localName) @@ -246,42 +192,28 @@ func (_m *SMTPClient) Hello(localName string) error { return r0 } -// SMTPClient_Hello_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Hello' -type SMTPClient_Hello_Call struct { +type SMTPClient_Mail struct { *mock.Call } -// Hello is a helper method to define mock.On call -// - localName string -func (_e *SMTPClient_Expecter) Hello(localName interface{}) *SMTPClient_Hello_Call { - return &SMTPClient_Hello_Call{Call: _e.mock.On("Hello", localName)} -} - -func (_c *SMTPClient_Hello_Call) Run(run func(localName string)) *SMTPClient_Hello_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) - }) - return _c +func (_m SMTPClient_Mail) Return(_a0 error) *SMTPClient_Mail { + return &SMTPClient_Mail{Call: _m.Call.Return(_a0)} } -func (_c *SMTPClient_Hello_Call) Return(_a0 error) *SMTPClient_Hello_Call { - _c.Call.Return(_a0) - return _c +func (_m *SMTPClient) OnMail(from string) *SMTPClient_Mail { + c_call := _m.On("Mail", from) + return &SMTPClient_Mail{Call: c_call} } -func (_c *SMTPClient_Hello_Call) RunAndReturn(run func(string) error) *SMTPClient_Hello_Call { - _c.Call.Return(run) - return _c +func (_m *SMTPClient) OnMailMatch(matchers ...interface{}) *SMTPClient_Mail { + c_call := _m.On("Mail", matchers...) + return &SMTPClient_Mail{Call: c_call} } // Mail provides a mock function with given fields: from func (_m *SMTPClient) Mail(from string) error { ret := _m.Called(from) - if len(ret) == 0 { - panic("no return value specified for Mail") - } - var r0 error if rf, ok := ret.Get(0).(func(string) error); ok { r0 = rf(from) @@ -292,42 +224,28 @@ func (_m *SMTPClient) Mail(from string) error { return r0 } -// SMTPClient_Mail_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Mail' -type SMTPClient_Mail_Call struct { +type SMTPClient_Noop struct { *mock.Call } -// Mail is a helper method to define mock.On call -// - from string -func (_e *SMTPClient_Expecter) Mail(from interface{}) *SMTPClient_Mail_Call { - return &SMTPClient_Mail_Call{Call: _e.mock.On("Mail", from)} -} - -func (_c *SMTPClient_Mail_Call) Run(run func(from string)) *SMTPClient_Mail_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) - }) - return _c +func (_m SMTPClient_Noop) Return(_a0 error) *SMTPClient_Noop { + return &SMTPClient_Noop{Call: _m.Call.Return(_a0)} } -func (_c *SMTPClient_Mail_Call) Return(_a0 error) *SMTPClient_Mail_Call { - _c.Call.Return(_a0) - return _c +func (_m *SMTPClient) OnNoop() *SMTPClient_Noop { + c_call := _m.On("Noop") + return &SMTPClient_Noop{Call: c_call} } -func (_c *SMTPClient_Mail_Call) RunAndReturn(run func(string) error) *SMTPClient_Mail_Call { - _c.Call.Return(run) - return _c +func (_m *SMTPClient) OnNoopMatch(matchers ...interface{}) *SMTPClient_Noop { + c_call := _m.On("Noop", matchers...) + return &SMTPClient_Noop{Call: c_call} } // Noop provides a mock function with given fields: func (_m *SMTPClient) Noop() error { ret := _m.Called() - if len(ret) == 0 { - panic("no return value specified for Noop") - } - var r0 error if rf, ok := ret.Get(0).(func() error); ok { r0 = rf() @@ -338,41 +256,28 @@ func (_m *SMTPClient) Noop() error { return r0 } -// SMTPClient_Noop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Noop' -type SMTPClient_Noop_Call struct { +type SMTPClient_Rcpt struct { *mock.Call } -// Noop is a helper method to define mock.On call -func (_e *SMTPClient_Expecter) Noop() *SMTPClient_Noop_Call { - return &SMTPClient_Noop_Call{Call: _e.mock.On("Noop")} +func (_m SMTPClient_Rcpt) Return(_a0 error) *SMTPClient_Rcpt { + return &SMTPClient_Rcpt{Call: _m.Call.Return(_a0)} } -func (_c *SMTPClient_Noop_Call) Run(run func()) *SMTPClient_Noop_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c +func (_m *SMTPClient) OnRcpt(to string) *SMTPClient_Rcpt { + c_call := _m.On("Rcpt", to) + return &SMTPClient_Rcpt{Call: c_call} } -func (_c *SMTPClient_Noop_Call) Return(_a0 error) *SMTPClient_Noop_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *SMTPClient_Noop_Call) RunAndReturn(run func() error) *SMTPClient_Noop_Call { - _c.Call.Return(run) - return _c +func (_m *SMTPClient) OnRcptMatch(matchers ...interface{}) *SMTPClient_Rcpt { + c_call := _m.On("Rcpt", matchers...) + return &SMTPClient_Rcpt{Call: c_call} } // Rcpt provides a mock function with given fields: to func (_m *SMTPClient) Rcpt(to string) error { ret := _m.Called(to) - if len(ret) == 0 { - panic("no return value specified for Rcpt") - } - var r0 error if rf, ok := ret.Get(0).(func(string) error); ok { r0 = rf(to) @@ -383,42 +288,28 @@ func (_m *SMTPClient) Rcpt(to string) error { return r0 } -// SMTPClient_Rcpt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Rcpt' -type SMTPClient_Rcpt_Call struct { +type SMTPClient_StartTLS struct { *mock.Call } -// Rcpt is a helper method to define mock.On call -// - to string -func (_e *SMTPClient_Expecter) Rcpt(to interface{}) *SMTPClient_Rcpt_Call { - return &SMTPClient_Rcpt_Call{Call: _e.mock.On("Rcpt", to)} +func (_m SMTPClient_StartTLS) Return(_a0 error) *SMTPClient_StartTLS { + return &SMTPClient_StartTLS{Call: _m.Call.Return(_a0)} } -func (_c *SMTPClient_Rcpt_Call) Run(run func(to string)) *SMTPClient_Rcpt_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) - }) - return _c +func (_m *SMTPClient) OnStartTLS(config *tls.Config) *SMTPClient_StartTLS { + c_call := _m.On("StartTLS", config) + return &SMTPClient_StartTLS{Call: c_call} } -func (_c *SMTPClient_Rcpt_Call) Return(_a0 error) *SMTPClient_Rcpt_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *SMTPClient_Rcpt_Call) RunAndReturn(run func(string) error) *SMTPClient_Rcpt_Call { - _c.Call.Return(run) - return _c +func (_m *SMTPClient) OnStartTLSMatch(matchers ...interface{}) *SMTPClient_StartTLS { + c_call := _m.On("StartTLS", matchers...) + return &SMTPClient_StartTLS{Call: c_call} } // StartTLS provides a mock function with given fields: config func (_m *SMTPClient) StartTLS(config *tls.Config) error { ret := _m.Called(config) - if len(ret) == 0 { - panic("no return value specified for StartTLS") - } - var r0 error if rf, ok := ret.Get(0).(func(*tls.Config) error); ok { r0 = rf(config) @@ -428,45 +319,3 @@ func (_m *SMTPClient) StartTLS(config *tls.Config) error { return r0 } - -// SMTPClient_StartTLS_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StartTLS' -type SMTPClient_StartTLS_Call struct { - *mock.Call -} - -// StartTLS is a helper method to define mock.On call -// - config *tls.Config -func (_e *SMTPClient_Expecter) StartTLS(config interface{}) *SMTPClient_StartTLS_Call { - return &SMTPClient_StartTLS_Call{Call: _e.mock.On("StartTLS", config)} -} - -func (_c *SMTPClient_StartTLS_Call) Run(run func(config *tls.Config)) *SMTPClient_StartTLS_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*tls.Config)) - }) - return _c -} - -func (_c *SMTPClient_StartTLS_Call) Return(_a0 error) *SMTPClient_StartTLS_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *SMTPClient_StartTLS_Call) RunAndReturn(run func(*tls.Config) error) *SMTPClient_StartTLS_Call { - _c.Call.Return(run) - return _c -} - -// NewSMTPClient creates a new instance of SMTPClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewSMTPClient(t interface { - mock.TestingT - Cleanup(func()) -}) *SMTPClient { - mock := &SMTPClient{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -}