From bbfc5ec7f676331b180ab769f2fc3cb29eabd84a Mon Sep 17 00:00:00 2001 From: "Glenn Y. Rolland" Date: Fri, 23 Nov 2018 11:46:12 +0100 Subject: [PATCH] Use cobra & viper for parsing parameters & environment --- cmd/trello2mail/config.go | 193 ++++++++++++++--------------------- cmd/trello2mail/email.go | 6 ++ cmd/trello2mail/main.go | 25 +++-- cmd/trello2mail/transport.go | 9 ++ cmd/trello2mail/trello.go | 8 ++ go.mod | 3 + go.sum | 31 ++++++ 7 files changed, 154 insertions(+), 121 deletions(-) diff --git a/cmd/trello2mail/config.go b/cmd/trello2mail/config.go index 14ea92f..c549e3a 100644 --- a/cmd/trello2mail/config.go +++ b/cmd/trello2mail/config.go @@ -1,140 +1,103 @@ package main import ( - "errors" + // "errors" "fmt" + "github.com/spf13/cobra" + "github.com/spf13/viper" "strings" // "github.com/davecgh/go-spew/spew" - "log" + // "log" "os" // "reflect" - "strconv" + // "strconv" ) -type ConfigEntry struct { - Type string - Ptr interface{} - Values []string -} +const ( + programBinary string = "trello2mail" +) -type TrelloConfig struct { - Url string - Token string -} +var ( + ALLOWED_AUTH_TYPES = []string{"none", "plain", "login"} + ALLOWED_SECURITY_TYPES = []string{"none", "tls", "starttls"} +) -type SmtpConfig struct { - Hostname string - Port uint16 - Username string - Password string - AuthType string - SecurityType string -} - -type EmailConfig struct { - From string - To []string - Subject string -} type Config struct { - Email EmailConfig - Smtp SmtpConfig - Trello TrelloConfig + EmailFrom string `mapstructure:"email-from"` + EmailTo []string `mapstructure:"email-to"` + EmailSubject string `mapstructure:"email-subject"` + + SmtpHostname string `mapstructure:"smtp-hostname"` + SmtpPort uint16 `mapstructure:"smtp-port"` + SmtpUsername string `mapstructure:"smtp-username"` + SmtpPassword string `mapstructure:"smtp-password"` + SmtpAuthType string `mapstructure:"smtp-auth-type"` + SmtpSecurityType string `mapstructure:"smtp-security-type"` + + TrelloUrl string `mapstructure:"trello-url"` + TrelloToken string `mapstructure:"trello-token"` + + Parser *cobra.Command `mapstructure:"-"` } func NewConfig() *Config { - return &Config{} + self := &Config{} + + cmd := &cobra.Command{ + Use: programBinary, + Run: func(cmd *cobra.Command, args []string) { /* placeholder */ }, + } + + viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) + viper.AutomaticEnv() + + cmd.PersistentFlags().StringVarP(&self.EmailFrom, "email-from", "", "", "address of sender") + cmd.PersistentFlags().StringArrayVarP(&self.EmailTo, "email-to", "", []string{}, "address(es) of recipient(s)") + cmd.PersistentFlags().StringVarP(&self.EmailSubject, "email-subject", "", "", "email subject") + viper.BindPFlag("email-from", cmd.PersistentFlags().Lookup("email-from")) + viper.BindPFlag("email-to", cmd.PersistentFlags().Lookup("email-to")) + viper.BindPFlag("email-subject", cmd.PersistentFlags().Lookup("email-subject")) + + cmd.PersistentFlags().StringVarP(&self.TrelloUrl, "trello-url", "", "", "url of trello board") + cmd.PersistentFlags().StringVarP(&self.TrelloToken, "trello-token", "", "", "url of trello token") + viper.BindPFlag("trello-url", cmd.PersistentFlags().Lookup("trello-url")) + viper.BindPFlag("trello-token", cmd.PersistentFlags().Lookup("trello-token")) + + cmd.PersistentFlags().StringVarP(&self.SmtpHostname, "smtp-hostname", "", "", "address of smtp server") + cmd.PersistentFlags().StringVarP(&self.SmtpUsername, "smtp-username", "", "", "username for smtp server") + cmd.PersistentFlags().StringVarP(&self.SmtpPassword, "smtp-password", "", "", "password for smtp server") + cmd.PersistentFlags().Uint16VarP(&self.SmtpPort, "smtp-port", "", 25, "port for smtp server") + cmd.PersistentFlags().StringVarP(&self.SmtpAuthType, "smtp-auth-type", "", "", "authentication type for smtp server") + cmd.PersistentFlags().StringVarP(&self.SmtpSecurityType, "smtp-security-type", "", "", "security type for smtp server") + viper.BindPFlag("smtp-hostname", cmd.PersistentFlags().Lookup("smtp-hostname")) + viper.BindPFlag("smtp-username", cmd.PersistentFlags().Lookup("smtp-username")) + viper.BindPFlag("smtp-password", cmd.PersistentFlags().Lookup("smtp-password")) + viper.BindPFlag("smtp-port", cmd.PersistentFlags().Lookup("smtp-port")) + viper.BindPFlag("smtp-auth-type", cmd.PersistentFlags().Lookup("smtp-auth-type")) + viper.BindPFlag("smtp-security-type", cmd.PersistentFlags().Lookup("smtp-security-type")) + + self.Parser = cmd + return self } -func (config *Config) ParseEnv() (int, error) { - // map env variables to config pointers - dataMap := map[string](ConfigEntry){ - "EMAIL_FROM": ConfigEntry{"string", &(config.Email.From), nil}, - "EMAIL_TO": ConfigEntry{"stringlist", &(config.Email.To), nil}, - "EMAIL_SUBJECT": ConfigEntry{"string", &(config.Email.Subject), nil}, - "TRELLO_URL": ConfigEntry{"string", &(config.Trello.Url), nil}, - "TRELLO_TOKEN": ConfigEntry{"string", &(config.Trello.Token), nil}, +func (self *Config) Parse() error { + // set config defaults + // persistent flags + // environment & config + // viper.SetEnvPrefix("") - "SMTP_HOSTNAME": ConfigEntry{"string", &(config.Smtp.Hostname), nil}, - "SMTP_USERNAME": ConfigEntry{"string", &(config.Smtp.Username), nil}, - "SMTP_PASSWORD": ConfigEntry{"string", &(config.Smtp.Password), nil}, - "SMTP_PORT": ConfigEntry{"uint16", &(config.Smtp.Port), nil}, + fmt.Printf("all: %#v\n", viper.AllSettings()) + fmt.Printf("email-from %s\n", viper.Get("email-from")) - "SMTP_AUTH_TYPE": ConfigEntry{"string", - &(config.Smtp.AuthType), []string{"none", "plain", "login"}}, - "SMTP_SECURITY_TYPE": ConfigEntry{"string", - &(config.Smtp.SecurityType), []string{"none", "tls", "starttls"}}, + if err := self.Parser.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) } - for envVar, mapEntry := range dataMap { - envValue := os.Getenv(envVar) - if len(envValue) == 0 { - errmsg := fmt.Sprintf( - "Empty environment variable. Please set %s value", - envVar, - ) - log.Panic(errors.New(errmsg)) - } - - if mapEntry.Values != nil { - allowedValue := false - for _, v := range mapEntry.Values { - if v == envValue { - allowedValue = true - } - } - if !allowedValue { - errmsg := fmt.Sprintf( - "Wrong value for %s=%s. Value must be one of %v", - envVar, - envValue, - mapEntry.Values, - ) - log.Panic(errors.New(errmsg)) - } - } - - switch mapEntry.Type { - case "string": - *(mapEntry.Ptr.(*string)) = envValue - - case "stringlist": - ptrs := strings.Split(envValue, ",") - mapEntry.Ptr = ptrs - - case "uint16": - u64, err := strconv.ParseUint(envValue, 10, 16) - if err != nil { - errmsg := fmt.Sprintf( - "Unable to convert %s=%s to unsigned int", - envVar, - envValue, - ) - log.Panic(errors.New(errmsg)) - } - *(mapEntry.Ptr.(*uint16)) = uint16(u64) - - case "bool": - b, err := strconv.ParseBool(envValue) - if err != nil { - errmsg := fmt.Sprintf( - "Unable to convert %s=%s to boolean", - envVar, - envValue, - ) - log.Panic(errors.New(errmsg)) - } - *(mapEntry.Ptr.(*bool)) = b - - default: - errmsg := fmt.Sprintf( - "Undefined parser for %s<%s>", - envVar, - mapEntry.Type, - ) - log.Panic(errors.New(errmsg)) - } + if err := viper.Unmarshal(&self); err != nil { + panic("Unable to unmarshal config") } + // spew.Dump(config) - return 0, nil + return nil } diff --git a/cmd/trello2mail/email.go b/cmd/trello2mail/email.go index a88a0d5..b4c40d9 100644 --- a/cmd/trello2mail/email.go +++ b/cmd/trello2mail/email.go @@ -17,6 +17,12 @@ import ( type EmailHeaders map[string]string +type EmailConfig struct { + From string + To []string + Subject string +} + type EmailCtx struct { Headers EmailHeaders BodyPlain string diff --git a/cmd/trello2mail/main.go b/cmd/trello2mail/main.go index ac2b1a7..3b44e79 100644 --- a/cmd/trello2mail/main.go +++ b/cmd/trello2mail/main.go @@ -9,22 +9,35 @@ import ( func main() { // Setup config config := NewConfig() - config.ParseEnv() + config.Parse() + fmt.Printf("%#v\n", config) // Get task list as markdown - trelloCtx := NewTrello(config.Trello.Token) - trelloBoard := trelloCtx.GetBoard(config.Trello.Url) + trelloCtx := NewTrello(config.TrelloToken) + trelloBoard := trelloCtx.GetBoard(config.TrelloUrl) trelloMarkdown := trelloBoard.ExportToMarkdown() trelloHtml := trelloBoard.ExportToHtml() - config.Email.Subject = fmt.Sprintf("Daily mail for %s", trelloBoard.Name) + config.EmailSubject = fmt.Sprintf("Daily mail for %s", trelloBoard.Name) // Create email enveloppe email := NewEmail() - email.SetHeaders(config.Email) + email.SetHeaders(EmailConfig{ + From: config.EmailFrom, + To: config.EmailTo, + Subject: config.EmailSubject, + }) email.SetBody(trelloHtml, trelloMarkdown) // Connect and send email - transport := NewTransport(config.Smtp) + transport := NewTransport(SmtpConfig{ + Hostname: config.SmtpHostname, + Port: config.SmtpPort, + Username: config.SmtpUsername, + Password: config.SmtpPassword, + AuthType: config.SmtpAuthType, + SecurityType: config.SmtpSecurityType, + }) + transport.Dial() transport.Authenticate() transport.Send(email) diff --git a/cmd/trello2mail/transport.go b/cmd/trello2mail/transport.go index 32fba2b..241b7f5 100644 --- a/cmd/trello2mail/transport.go +++ b/cmd/trello2mail/transport.go @@ -8,6 +8,15 @@ import ( "net/smtp" ) +type SmtpConfig struct { + Hostname string + Port uint16 + Username string + Password string + AuthType string + SecurityType string +} + type TransportCtx struct { Config SmtpConfig Address string diff --git a/cmd/trello2mail/trello.go b/cmd/trello2mail/trello.go index 82c687b..838eb31 100644 --- a/cmd/trello2mail/trello.go +++ b/cmd/trello2mail/trello.go @@ -17,6 +17,11 @@ const ( APP_KEY string = "80dbcf6f88f62cc5639774e13342c20b" ) +type TrelloConfig struct { + Url string + Token string +} + type TrelloCtx struct { Token string Client *trello.Client @@ -76,6 +81,9 @@ func (ctx *TrelloCtx) GetBoard(boardUrl string) TrelloBoard { boardId := strings.Split(parsedUrl.Path, "/")[2] board, err := ctx.Client.GetBoard(boardId, trello.Defaults()) + if err != nil { + log.Panic(err) + } return TrelloBoard{Ctx: ctx, Ptr: board, Name: board.Name} } diff --git a/go.mod b/go.mod index b44f038..fc508b9 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,7 @@ require ( github.com/pkg/errors v0.8.0 // indirect github.com/russross/blackfriday/v2 v2.0.1 github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect + github.com/spf13/cobra v0.0.3 + github.com/spf13/pflag v1.0.3 // indirect + github.com/spf13/viper v1.2.1 ) diff --git a/go.sum b/go.sum index 026fde4..be260c3 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,39 @@ github.com/adlio/trello v0.0.0-20180621142300-8a458717123e h1:i5+hZJx4iTPT4+ojV5zi50UIPUxs7CH8r+P3BFB4Ofs= github.com/adlio/trello v0.0.0-20180621142300-8a458717123e/go.mod h1:VjzhFGdnEJGih1AsIWi/yU6y01yZh0DuEWCNAyOJ1WE= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I= +github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY= github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg= +github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= +github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.2.1 h1:bIcUwXqLseLF3BDAZduuNfekWG87ibtFxi59Bq+oI9M= +github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI= +golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992 h1:BH3eQWeGbwRU2+wxxuuPOdFBmaiBH81O8BugSjHeTFg= +golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=