diff --git a/app/cmd/flg/notifier.go b/app/cmd/flg/notifier.go index 2fb3fff..0c82031 100644 --- a/app/cmd/flg/notifier.go +++ b/app/cmd/flg/notifier.go @@ -16,12 +16,13 @@ import ( // NotifyGroup defines parameters for the notifier. type NotifyGroup struct { - Telegram TelegramGroup `group:"telegram" namespace:"telegram" env-namespace:"TELEGRAM"` - Github GithubNotifierGroup `group:"github" namespace:"github" env-namespace:"GITHUB"` - Mattermost MattermostGroup `group:"mattermost" namespace:"mattermost" env-namespace:"MATTERMOST"` - Stdout bool `long:"stdout" env:"STDOUT" description:"print release notes to stdout"` - ConfLocation string `long:"conf_location" env:"CONF_LOCATION" description:"location to the config file"` - Extras map[string]string `long:"extras" env:"EXTRAS" env-delim:"," description:"extra variables to use in the template"` + Telegram TelegramGroup `group:"telegram" namespace:"telegram" env-namespace:"TELEGRAM"` + Github GithubNotifierGroup `group:"github" namespace:"github" env-namespace:"GITHUB"` + Mattermost MattermostGroup `group:"mattermost" namespace:"mattermost" env-namespace:"MATTERMOST"` + MattermostHook MattermostHookGroup `group:"mattermost-hook" namespace:"mattermost-hook" env-namespace:"MATTERMOST_HOOK"` + Stdout bool `long:"stdout" env:"STDOUT" description:"print release notes to stdout"` + ConfLocation string `long:"conf_location" env:"CONF_LOCATION" description:"location to the config file"` + Extras map[string]string `long:"extras" env:"EXTRAS" env-delim:"," description:"extra variables to use in the template"` } // GithubNotifierGroup defines parameters to make release in the github. @@ -61,12 +62,24 @@ func (g MattermostGroup) Empty() bool { return g.BaseURL == "" || g.ChannelID == "" || g.LoginID == "" || g.Password == "" } +// MattermostHookGroup defines parameters for mattermost hook notifier. +type MattermostHookGroup struct { + BaseURL string `long:"base_url" env:"BASE_URL" description:"base url of the mattermost server"` + ID string `long:"id" env:"ID" description:"id of the hook, where the release notes will be sent"` +} + +// Empty returns true if the config group is not filled. +func (g MattermostHookGroup) Empty() bool { + return g.BaseURL == "" || g.ID == "" +} + // Build builds the notifier. func (r *NotifyGroup) Build() (destinations notify.Destinations, err error) { logger := log.Default() if r.Stdout { destinations = append(destinations, ¬ify.WriterNotifier{Writer: os.Stdout, Name: "stdout"}) + log.Printf("[INFO] printing notes to stdout is enabled") } if !r.Telegram.Empty() { @@ -77,6 +90,7 @@ func (r *NotifyGroup) Build() (destinations notify.Destinations, err error) { Token: r.Telegram.Token, DisableWebPagePreview: !r.Telegram.WebPagePreview, })) + log.Printf("[INFO] telegram notifier is enabled") } if !r.Github.Empty() { @@ -93,10 +107,11 @@ func (r *NotifyGroup) Build() (destinations notify.Destinations, err error) { } destinations = append(destinations, gh) + log.Printf("[INFO] github releases notifier is enabled") } if !r.Mattermost.Empty() { - mm, err := notify.NewMattermost(notify.MattermostParams{ + mm, err := notify.NewMattermostBot(notify.MattermostBotParams{ Client: http.Client{Timeout: 5 * time.Second}, BaseURL: r.Mattermost.BaseURL, ChannelID: r.Mattermost.ChannelID, @@ -109,6 +124,16 @@ func (r *NotifyGroup) Build() (destinations notify.Destinations, err error) { } destinations = append(destinations, mm) + log.Printf("[INFO] mattermost bot notifier is enabled") + } + + if !r.MattermostHook.Empty() { + destinations = append(destinations, notify.NewMattermostHook( + http.Client{Timeout: 5 * time.Second}, + r.MattermostHook.BaseURL, + r.MattermostHook.ID, + )) + log.Printf("[INFO] mattermost hook notifier is enabled") } return destinations, nil diff --git a/app/notify/mattermost.go b/app/notify/mattermost.go index 42fa201..03a95d9 100644 --- a/app/notify/mattermost.go +++ b/app/notify/mattermost.go @@ -15,9 +15,9 @@ import ( "github.com/go-pkgz/requester/middleware" ) -// Mattermost is a notifier for Mattermost. -type Mattermost struct { - MattermostParams +// MattermostBot is a notifier for MattermostBot. +type MattermostBot struct { + MattermostBotParams token struct { value string mu *sync.Mutex @@ -25,8 +25,8 @@ type Mattermost struct { cl *http.Client } -// MattermostParams are Mattermost notifier params. -type MattermostParams struct { +// MattermostBotParams are MattermostBot notifier params. +type MattermostBotParams struct { Client http.Client BaseURL string ChannelID string @@ -36,11 +36,11 @@ type MattermostParams struct { LDAP bool } -// NewMattermost makes a new Mattermost notifier. -func NewMattermost(params MattermostParams) (*Mattermost, error) { +// NewMattermostBot makes a new MattermostBot notifier. +func NewMattermostBot(params MattermostBotParams) (*MattermostBot, error) { params.BaseURL = strings.TrimRight(params.BaseURL, "/") - svc := &Mattermost{MattermostParams: params} + svc := &MattermostBot{MattermostBotParams: params} svc.token.mu = &sync.Mutex{} svc.cl = requester.New(svc.Client, svc.reloginMiddleware).Client() @@ -56,7 +56,7 @@ func NewMattermost(params MattermostParams) (*Mattermost, error) { return svc, nil } -func (m *Mattermost) login(ctx context.Context) (token string, err error) { +func (m *MattermostBot) login(ctx context.Context) (token string, err error) { ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() @@ -97,7 +97,7 @@ func (m *Mattermost) login(ctx context.Context) (token string, err error) { return m.token.value, nil } -func (m *Mattermost) ping(ctx context.Context) error { +func (m *MattermostBot) ping(ctx context.Context) error { u := fmt.Sprintf("%s/api/v4/config/client?format=old", m.BaseURL) req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, http.NoBody) @@ -118,12 +118,12 @@ func (m *Mattermost) ping(ctx context.Context) error { } // String returns the name of the notifier. -func (m *Mattermost) String() string { - return fmt.Sprintf("mattermost at: %s", m.BaseURL) +func (m *MattermostBot) String() string { + return fmt.Sprintf("mattermost bot at: %s", m.BaseURL) } // Send sends a message to Mattermost. -func (m *Mattermost) Send(ctx context.Context, _, text string) error { +func (m *MattermostBot) Send(ctx context.Context, _, text string) error { u := fmt.Sprintf("%s/api/v4/posts", m.BaseURL) b, err := json.Marshal(map[string]string{"channel_id": m.ChannelID, "message": text}) @@ -148,7 +148,7 @@ func (m *Mattermost) Send(ctx context.Context, _, text string) error { return nil } -func (m *Mattermost) reloginMiddleware(next http.RoundTripper) http.RoundTripper { +func (m *MattermostBot) reloginMiddleware(next http.RoundTripper) http.RoundTripper { return middleware.RoundTripperFunc(func(req *http.Request) (*http.Response, error) { resp, err := next.RoundTrip(req) if err != nil { @@ -168,3 +168,46 @@ func (m *Mattermost) reloginMiddleware(next http.RoundTripper) http.RoundTripper return resp, nil }) } + +// MattermostHook sends messages to Mattermost via webhook. +type MattermostHook struct { + cl *http.Client + baseURL string + hookID string +} + +// NewMattermostHook makes a new MattermostHook notifier. +func NewMattermostHook(cl http.Client, baseURL, hookID string) *MattermostHook { + return &MattermostHook{cl: &cl, baseURL: baseURL, hookID: hookID} +} + +// String returns the name of the notifier. +func (m *MattermostHook) String() string { + return fmt.Sprintf("mattermost hook at: %s", m.baseURL) +} + +// Send sends a message to Mattermost. +func (m *MattermostHook) Send(ctx context.Context, _, text string) error { + u := fmt.Sprintf("%s/hooks/%s", m.baseURL, m.hookID) + + b, err := json.Marshal(map[string]string{"text": text}) + if err != nil { + return fmt.Errorf("marshal body: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, bytes.NewReader(b)) + if err != nil { + return fmt.Errorf("build request: %w", err) + } + + resp, err := m.cl.Do(req) + if err != nil { + return fmt.Errorf("do request: %w", err) + } + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + return nil +}