-
Notifications
You must be signed in to change notification settings - Fork 218
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(service): Adds Mattermost service (#516)
* feat(service): Adds Mattermost service * refactor: Removes log msgs fix: Adds PreSend and PostSend capabilities to user docs: Updates doc.go and README.md * refactor: Adds test cases Adds presend and postsend tests for mattermost service * refactor: Updates mattermost test cases * refactor: update mattermost test cases * refactor: update mattermost hook(s) test cases --------- Co-authored-by: EthanEFung <[email protected]> Co-authored-by: Niko Köser <[email protected]>
- Loading branch information
1 parent
67c5b79
commit 6e1878c
Showing
7 changed files
with
514 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
# Mattermost Usage | ||
|
||
Ensure that you have already navigated to your GOPATH and installed the following packages: | ||
|
||
* `go get -u github.com/nikoksr/notify` | ||
|
||
## Steps for Mattermost Server | ||
|
||
These are general and very high level instructions | ||
|
||
1. Create a new Mattermost server / Join existing Mattermost server | ||
2. Make sure your Username/loginID have the OAuth permission scope(s): `create_post` | ||
3. Copy the *Channel ID* of the channel you want to post a message to. You can grab the *Channel ID* in channel info. example: *yfgstwuisnshydhd* | ||
4. Now you should be good to use the code below | ||
|
||
## Sample Code | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"os" | ||
|
||
"github.com/nikoksr/notify" | ||
"github.com/nikoksr/notify/service/mattermost" | ||
) | ||
|
||
func main() { | ||
|
||
// Init notifier | ||
notifier := notify.New() | ||
ctx := context.Background() | ||
|
||
// Provide your Mattermost server url | ||
mattermostService := mattermost.New("https://myserver.cloud.mattermost.com") | ||
|
||
// Provide username as loginID and password to login into above server. | ||
// NOTE: This generates auth token which will get expired, invoking this method again | ||
// after expiry will generate new token and uses for further requests. | ||
err := mattermostService.LoginWithCredentials(ctx, "[email protected]", "somepassword") | ||
if err != nil { | ||
fmt.Println(err) | ||
os.Exit(1) | ||
} | ||
|
||
// Passing a Mattermost channel/chat id as receiver for our messages. | ||
// Where to send our messages. | ||
mattermostService.AddReceivers("CHANNEL_ID") | ||
|
||
// Tell our notifier to use the Mattermost service. You can repeat the above process | ||
// for as many services as you like and just tell the notifier to use them. | ||
notifier.UseServices(mattermostService) | ||
|
||
// Add presend and postsend hooks that you need to execute before every requests and after | ||
// every response respectively. Multiple presend and postsend are executed in the order defined here. | ||
// refer service/http for the more info. | ||
// PreSend hook | ||
mattermostService.PreSend(func(req *stdhttp.Request) error { | ||
log.Printf("Sending message to %s server", req.URL) | ||
return nil | ||
}) | ||
// PostSend hook | ||
mattermostService.PostSend(func(req *stdhttp.Request, resp *stdhttp.Response) error { | ||
log.Printf("Message sent to %s server with status %d", req.URL, resp.StatusCode) | ||
return nil | ||
}) | ||
|
||
// Send a message | ||
err = notifier.Send( | ||
ctx, | ||
"Hello from notify :wave:\n", | ||
"Message written in Go!", | ||
) | ||
if err != nil { | ||
fmt.Println(err) | ||
os.Exit(1) | ||
} | ||
|
||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/* | ||
Package mattermost provides message notification integration for mattermost.com. | ||
Usage: | ||
package main | ||
import ( | ||
"os" | ||
"github.com/nikoksr/notify" | ||
"github.com/nikoksr/notify/service/mattermost" | ||
) | ||
func main() { | ||
// Init notifier | ||
notifier := notify.New() | ||
ctx := context.Background() | ||
// Provide your Mattermost server url | ||
mattermostService := mattermost.New("https://myserver.cloud.mattermost.com") | ||
// Provide username as loginID and password to login into above server. | ||
// NOTE: This generates auth token which will get expired, invoking this method again | ||
// after expiry will generate new token and uses for further requests. | ||
err := mattermostService.LoginWithCredentials(ctx, "[email protected]", "somepassword") | ||
if err != nil { | ||
fmt.Println(err) | ||
os.Exit(1) | ||
} | ||
// Passing a Mattermost channel/chat id as receiver for our messages. | ||
// Where to send our messages. | ||
mattermostService.AddReceivers("CHANNEL_ID") | ||
// Tell our notifier to use the Mattermost service. You can repeat the above process | ||
// for as many services as you like and just tell the notifier to use them. | ||
notifier.UseServices(mattermostService) | ||
// Add presend and postsend hooks that you need to execute before every requests and after | ||
// every response respectively. Multiple presend and postsend are executed in the order defined here. | ||
// refer service/http for the more info. | ||
// PreSend hook | ||
mattermostService.PreSend(func(req *stdhttp.Request) error { | ||
log.Printf("Sending message to %s server", req.URL) | ||
return nil | ||
}) | ||
// PostSend hook | ||
mattermostService.PostSend(func(req *stdhttp.Request, resp *stdhttp.Response) error { | ||
log.Printf("Message sent to %s server with status %d", req.URL, resp.StatusCode) | ||
return nil | ||
}) | ||
// Send a message | ||
err = notifier.Send( | ||
ctx, | ||
"Hello from notify :wave:", | ||
"Message written in Go!", | ||
) | ||
if err != nil { | ||
fmt.Println(err) | ||
os.Exit(1) | ||
} | ||
} | ||
*/ | ||
package mattermost |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
// Package mattermost provides message notification integration for mattermost.com. | ||
package mattermost | ||
|
||
import ( | ||
"context" | ||
"io" | ||
stdhttp "net/http" | ||
|
||
"github.com/pkg/errors" | ||
|
||
"github.com/nikoksr/notify/service/http" | ||
) | ||
|
||
//go:generate mockery --name=httpClient --output=. --case=underscore --inpackage | ||
type httpClient interface { | ||
AddReceivers(wh ...*http.Webhook) | ||
PreSend(prefn http.PreSendHookFn) | ||
Send(ctx context.Context, subject, message string) error | ||
PostSend(postfn http.PostSendHookFn) | ||
} | ||
|
||
// Service encapsulates the notify httpService client and contains mattermost channel ids. | ||
type Service struct { | ||
loginClient httpClient | ||
messageClient httpClient | ||
channelIDs map[string]bool | ||
} | ||
|
||
// New returns a new instance of a Mattermost notification service. | ||
func New(url string) *Service { | ||
httpService := setupMsgService(url) | ||
return &Service{ | ||
setupLoginService(url, httpService), | ||
httpService, | ||
make(map[string]bool), | ||
} | ||
} | ||
|
||
// LoginWithCredentials provides helper for authentication using Mattermost user/admin credentials. | ||
func (s *Service) LoginWithCredentials(ctx context.Context, loginID, password string) error { | ||
// request login | ||
if err := s.loginClient.Send(ctx, loginID, password); err != nil { | ||
return errors.Wrapf(err, "failed login to Mattermost server") | ||
} | ||
return nil | ||
} | ||
|
||
// AddReceivers takes Mattermost channel IDs or Chat IDs and adds them to the internal channel ID list. | ||
// The Send method will send a given message to all these channels. | ||
func (s *Service) AddReceivers(channelIDs ...string) { | ||
for i := range channelIDs { | ||
s.channelIDs[channelIDs[i]] = true | ||
} | ||
} | ||
|
||
// Send takes a message subject and a message body and send them to added channel ids. | ||
// you will need a 'create_post' permission for your username. | ||
// refer https://api.mattermost.com/ for more info | ||
func (s *Service) Send(ctx context.Context, subject, message string) error { | ||
for id := range s.channelIDs { | ||
select { | ||
case <-ctx.Done(): | ||
return ctx.Err() | ||
default: | ||
// create post | ||
if err := s.messageClient.Send(ctx, id, subject+"\n"+message); err != nil { | ||
return errors.Wrapf(err, "failed to send message") | ||
} | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// PreSend adds a pre-send hook to the service. The hook will be executed before sending a request to a receiver. | ||
func (s *Service) PreSend(hook http.PreSendHookFn) { | ||
s.messageClient.PreSend(hook) | ||
} | ||
|
||
// PostSend adds a post-send hook to the service. The hook will be executed after sending a request to a receiver. | ||
func (s *Service) PostSend(hook http.PostSendHookFn) { | ||
s.messageClient.PostSend(hook) | ||
} | ||
|
||
// setups main message service for creating posts | ||
func setupMsgService(url string) *http.Service { | ||
// create new http client for sending messages/notifications | ||
httpService := http.New() | ||
|
||
// add custom payload builder | ||
httpService.AddReceivers(&http.Webhook{ | ||
URL: url + "/api/v4/posts", | ||
Header: stdhttp.Header{}, | ||
ContentType: "application/json", | ||
Method: stdhttp.MethodPost, | ||
BuildPayload: func(channelID, subjectAndMessage string) (payload any) { | ||
return map[string]string{ | ||
"channel_id": channelID, | ||
"message": subjectAndMessage, | ||
} | ||
}, | ||
}) | ||
|
||
// add post-send hook for error checks | ||
httpService.PostSend(func(req *stdhttp.Request, resp *stdhttp.Response) error { | ||
if resp.StatusCode != stdhttp.StatusCreated { | ||
b, _ := io.ReadAll(resp.Body) | ||
return errors.New("failed to create post with status: " + resp.Status + " body: " + string(b)) | ||
} | ||
return nil | ||
}) | ||
return httpService | ||
} | ||
|
||
// setups login service to get token | ||
func setupLoginService(url string, msgService *http.Service) *http.Service { | ||
// create another new http client for login request call. | ||
httpService := http.New() | ||
|
||
// append login path for the given mattermost server with custom payload builder. | ||
httpService.AddReceivers(&http.Webhook{ | ||
URL: url + "/api/v4/users/login", | ||
Header: stdhttp.Header{}, | ||
ContentType: "application/json", | ||
Method: stdhttp.MethodPost, | ||
BuildPayload: func(loginID, password string) (payload any) { | ||
return map[string]string{ | ||
"login_id": loginID, | ||
"password": password, | ||
} | ||
}, | ||
}) | ||
|
||
// Add post-send hook to do error checks and log the response after it is received. | ||
// Also extract token from response header and set it as part of pre-send hook of main http client for further requests. | ||
httpService.PostSend(func(req *stdhttp.Request, resp *stdhttp.Response) error { | ||
if resp.StatusCode != stdhttp.StatusOK { | ||
b, _ := io.ReadAll(resp.Body) | ||
return errors.New("login failed with status: " + resp.Status + " body: " + string(b)) | ||
} | ||
|
||
// get token from header | ||
token := resp.Header.Get("Token") | ||
if token == "" { | ||
return errors.New("received empty token") | ||
} | ||
|
||
// set token as pre-send hook | ||
msgService.PreSend(func(req *stdhttp.Request) error { | ||
req.Header.Set("Authorization", "Bearer "+token) | ||
return nil | ||
}) | ||
return nil | ||
}) | ||
return httpService | ||
} |
Oops, something went wrong.