From 08cd3d90b808c45c762433d4b385bf8d7c79e53d Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Sun, 1 Mar 2020 17:09:22 +0100 Subject: [PATCH] Add Rocket.Chat notifier (#44) --- .res/notif-rocketchat.png | Bin 0 -> 11047 bytes cmd/main.go | 2 +- doc/configuration.md | 41 ++++++--- doc/notifications.md | 9 +- internal/config/config.go | 12 ++- internal/config/config.test.yml | 19 ++-- internal/config/config_test.go | 22 +++-- internal/logging/logger.go | 2 +- internal/model/flags.go | 2 +- internal/model/notif.go | 39 +++++--- internal/notif/client.go | 10 +- internal/notif/rocketchat/client.go | 136 ++++++++++++++++++++++++++++ internal/notif/rocketchat/model.go | 41 +++++++++ internal/notif/slack/slack.go | 62 +++++++------ pkg/registry/image.go | 3 +- pkg/registry/manifest.go | 1 + pkg/registry/tags.go | 2 + 17 files changed, 321 insertions(+), 82 deletions(-) create mode 100644 .res/notif-rocketchat.png create mode 100644 internal/notif/rocketchat/client.go create mode 100644 internal/notif/rocketchat/model.go diff --git a/.res/notif-rocketchat.png b/.res/notif-rocketchat.png new file mode 100644 index 0000000000000000000000000000000000000000..45889abc973ad018932bb22b5f421086b2489f29 GIT binary patch literal 11047 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 literal 0 HcmV?d00001 diff --git a/cmd/main.go b/cmd/main.go index d911d6cf..5058b22a 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -28,7 +28,7 @@ func main() { kingpin.Flag("config", "Diun configuration file.").Envar("CONFIG").Required().StringVar(&flags.Cfgfile) kingpin.Flag("timezone", "Timezone assigned to Diun.").Envar("TZ").Default("UTC").StringVar(&flags.Timezone) kingpin.Flag("log-level", "Set log level.").Envar("LOG_LEVEL").Default("info").StringVar(&flags.LogLevel) - kingpin.Flag("log-json", "Enable JSON logging output.").Envar("LOG_JSON").Default("false").BoolVar(&flags.LogJson) + kingpin.Flag("log-json", "Enable JSON logging output.").Envar("LOG_JSON").Default("false").BoolVar(&flags.LogJSON) kingpin.Flag("log-caller", "Enable to add file:line of the caller.").Envar("LOG_CALLER").Default("false").BoolVar(&flags.LogCaller) kingpin.UsageTemplate(kingpin.CompactUsageTemplate).Version(version).Author("CrazyMax") kingpin.CommandLine.Name = "diun" diff --git a/doc/configuration.md b/doc/configuration.md index a64cdca7..c834c515 100644 --- a/doc/configuration.md +++ b/doc/configuration.md @@ -22,6 +22,12 @@ watch: first_check_notif: false notif: + gotify: + enable: false + endpoint: http://gotify.foo.com + token: Token123456 + priority: 1 + timeout: 10 mail: enable: false host: localhost @@ -32,6 +38,13 @@ notif: password: from: to: + rocketchat: + enable: false + endpoint: http://rocket.foo.com:3000 + channel: "#general" + user_id: abcdEFGH012345678 + token: Token123456 + timeout: 10 slack: enable: false webhook_url: https://hooks.slack.com/services/ABCD12EFG/HIJK34LMN/01234567890abcdefghij @@ -49,12 +62,6 @@ notif: Content-Type: application/json Authorization: Token123456 timeout: 10 - gotify: - enable: false - endpoint: http://gotify.foo.com - token: Token123456 - priority: 1 - timeout: 10 regopts: someregistryoptions: @@ -123,6 +130,13 @@ providers: ### notif +* `gotify` + * `enable`: Enable gotify notification (default: `false`). + * `endpoint`: Gotify base URL (e.g. `http://gotify.foo.com`). **required** + * `token`: Application token. **required** + * `priority`: The priority of the message. + * `timeout`: Timeout specifies a time limit for the request to be made. (default: `10`). + * `mail` * `enable`: Enable email reports (default: `false`). * `host`: SMTP server host (default: `localhost`). **required** @@ -136,6 +150,14 @@ providers: * `from`: Sender email address. **required** * `to`: Recipient email address. **required** +* `rocketchat` + * `enable`: Enable Rocket.Chat notification (default: `false`). + * `endpoint`: Rocket.Chat base URL (e.g. `http://rocket.foo.com:3000`). **required** + * `channel`: Channel name with the prefix in front of it. **required** + * `user_id`: User ID. **required** + * `token`: Authentication token. **required** + * `timeout`: Timeout specifies a time limit for the request to be made. (default: `10`). + * `slack` * `enable`: Enable slack notification (default: `false`). * `webhook_url`: Slack [incoming webhook URL](https://api.slack.com/messaging/webhooks). **required** @@ -152,13 +174,6 @@ providers: * `headers`: Map of additional headers to be sent. * `timeout`: Timeout specifies a time limit for the request to be made. (default: `10`). -* `gotify` - * `enable`: Enable gotify notification (default: `false`). - * `endpoint`: Gotify base URL (e.g. `http://gotify.foo.com`). **required** - * `token`: Application token. **required** - * `priority`: The priority of the message. - * `timeout`: Timeout specifies a time limit for the request to be made. (default: `10`). - ### regopts * `username`: Registry username. diff --git a/doc/notifications.md b/doc/notifications.md index fbf7be08..ed8b7c42 100644 --- a/doc/notifications.md +++ b/doc/notifications.md @@ -2,6 +2,7 @@ * [Gotify](#gotify) * [Mail](#mail) +* [Rocket.Chat](#rocketchat) * [Slack](#slack) * [Telegram](#telegram) * [Webhook](#webhook) @@ -18,9 +19,15 @@ Here is an email sample if you add `mail` notification: ![](../.res/notif-mail.png) +## Rocket.Chat + +To be able to send notifications to your Rocket.Chat channel, you must first create a Personal Access Token through your account settings: + +![](../.res/notif-rocketchat.png) + ## Slack -You can send notifications to your slack channel using an [incoming webhook URL](https://api.slack.com/messaging/webhooks): +You can send notifications to your Slack channel using an [incoming webhook URL](https://api.slack.com/messaging/webhooks): ![](../.res/notif-slack.png) diff --git a/internal/config/config.go b/internal/config/config.go index 1270ceea..7efc5478 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -49,6 +49,10 @@ func Load(flags model.Flags, version string) (*Config, error) { FirstCheckNotif: false, }, Notif: model.Notif{ + Gotify: model.NotifGotify{ + Enable: false, + Timeout: 10, + }, Mail: model.NotifMail{ Enable: false, Host: "localhost", @@ -56,6 +60,10 @@ func Load(flags model.Flags, version string) (*Config, error) { SSL: false, InsecureSkipVerify: false, }, + RocketChat: model.NotifRocketChat{ + Enable: false, + Timeout: 10, + }, Slack: model.NotifSlack{ Enable: false, }, @@ -67,10 +75,6 @@ func Load(flags model.Flags, version string) (*Config, error) { Method: "GET", Timeout: 10, }, - Gotify: model.NotifGotify{ - Enable: false, - Timeout: 10, - }, }, } diff --git a/internal/config/config.test.yml b/internal/config/config.test.yml index 3cf2594e..03efe58c 100644 --- a/internal/config/config.test.yml +++ b/internal/config/config.test.yml @@ -7,6 +7,12 @@ watch: first_check_notif: false notif: + gotify: + enable: false + endpoint: http://gotify.foo.com + token: Token123456 + priority: 1 + timeout: 10 mail: enable: false host: localhost @@ -19,6 +25,13 @@ notif: password_file: from: to: + rocketchat: + enable: false + endpoint: http://rocket.foo.com:3000 + channel: "#general" + user_id: abcdEFGH012345678 + token: Token123456 + timeout: 10 slack: enable: false webhook_url: https://hooks.slack.com/services/ABCD12EFG/HIJK34LMN/01234567890abcdefghij @@ -36,12 +49,6 @@ notif: Content-Type: application/json Authorization: Token123456 timeout: 10 - gotify: - enable: false - endpoint: http://gotify.foo.com - token: Token123456 - priority: 1 - timeout: 10 regopts: someregopts: diff --git a/internal/config/config_test.go b/internal/config/config_test.go index d4f59d81..580f5747 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -52,6 +52,13 @@ func TestLoad(t *testing.T) { Schedule: "*/30 * * * *", }, Notif: model.Notif{ + Gotify: model.NotifGotify{ + Enable: false, + Endpoint: "http://gotify.foo.com", + Token: "Token123456", + Priority: 1, + Timeout: 10, + }, Mail: model.NotifMail{ Enable: false, Host: "localhost", @@ -59,6 +66,14 @@ func TestLoad(t *testing.T) { SSL: false, InsecureSkipVerify: false, }, + RocketChat: model.NotifRocketChat{ + Enable: false, + Endpoint: "http://rocket.foo.com:3000", + Channel: "#general", + UserID: "abcdEFGH012345678", + Token: "Token123456", + Timeout: 10, + }, Slack: model.NotifSlack{ Enable: false, WebhookURL: "https://hooks.slack.com/services/ABCD12EFG/HIJK34LMN/01234567890abcdefghij", @@ -78,13 +93,6 @@ func TestLoad(t *testing.T) { }, Timeout: 10, }, - Gotify: model.NotifGotify{ - Enable: false, - Endpoint: "http://gotify.foo.com", - Token: "Token123456", - Priority: 1, - Timeout: 10, - }, }, RegOpts: map[string]model.RegOpts{ "someregopts": { diff --git a/internal/logging/logger.go b/internal/logging/logger.go index ec82be03..05e21a2c 100644 --- a/internal/logging/logger.go +++ b/internal/logging/logger.go @@ -21,7 +21,7 @@ func Configure(fl *model.Flags, location *time.Location) { return time.Now().In(location) } - if !fl.LogJson { + if !fl.LogJSON { w = zerolog.ConsoleWriter{ Out: os.Stdout, TimeFormat: time.RFC1123, diff --git a/internal/model/flags.go b/internal/model/flags.go index d4b636e8..3f058520 100644 --- a/internal/model/flags.go +++ b/internal/model/flags.go @@ -5,6 +5,6 @@ type Flags struct { Cfgfile string Timezone string LogLevel string - LogJson bool + LogJSON bool LogCaller bool } diff --git a/internal/model/notif.go b/internal/model/notif.go index 2539fdda..c56c2713 100644 --- a/internal/model/notif.go +++ b/internal/model/notif.go @@ -14,11 +14,21 @@ type NotifEntry struct { // Notif holds data necessary for notification configuration type Notif struct { - Mail NotifMail `yaml:"mail,omitempty"` - Slack NotifSlack `yaml:"slack,omitempty"` - Telegram NotifTelegram `yaml:"telegram,omitempty"` - Webhook NotifWebhook `yaml:"webhook,omitempty"` - Gotify NotifGotify `yaml:"gotify,omitempty"` + Gotify NotifGotify `yaml:"gotify,omitempty"` + Mail NotifMail `yaml:"mail,omitempty"` + RocketChat NotifRocketChat `yaml:"rocketchat,omitempty"` + Slack NotifSlack `yaml:"slack,omitempty"` + Telegram NotifTelegram `yaml:"telegram,omitempty"` + Webhook NotifWebhook `yaml:"webhook,omitempty"` +} + +// NotifGotify holds gotify notification configuration details +type NotifGotify struct { + Enable bool `yaml:"enable,omitempty"` + Endpoint string `yaml:"endpoint,omitempty"` + Token string `yaml:"token,omitempty"` + Priority int `yaml:"priority,omitempty"` + Timeout int `yaml:"timeout,omitempty"` } // NotifMail holds mail notification configuration details @@ -36,6 +46,16 @@ type NotifMail struct { To string `yaml:"to,omitempty"` } +// NotifRocketChat holds Rocket.Chat notification configuration details +type NotifRocketChat struct { + Enable bool `yaml:"enable,omitempty"` + Endpoint string `yaml:"endpoint,omitempty"` + Channel string `yaml:"channel,omitempty"` + UserID string `yaml:"user_id,omitempty"` + Token string `yaml:"token,omitempty"` + Timeout int `yaml:"timeout,omitempty"` +} + // NotifSlack holds slack notification configuration details type NotifSlack struct { Enable bool `yaml:"enable,omitempty"` @@ -57,12 +77,3 @@ type NotifWebhook struct { Headers map[string]string `yaml:"headers,omitempty"` Timeout int `yaml:"timeout,omitempty"` } - -// NotifGotify holds gotify notification configuration details -type NotifGotify struct { - Enable bool `yaml:"enable,omitempty"` - Endpoint string `yaml:"endpoint,omitempty"` - Token string `yaml:"token,omitempty"` - Priority int `yaml:"priority,omitempty"` - Timeout int `yaml:"timeout,omitempty"` -} diff --git a/internal/notif/client.go b/internal/notif/client.go index c68b7b1d..efb6a4f4 100644 --- a/internal/notif/client.go +++ b/internal/notif/client.go @@ -5,6 +5,7 @@ import ( "github.com/crazy-max/diun/internal/notif/gotify" "github.com/crazy-max/diun/internal/notif/mail" "github.com/crazy-max/diun/internal/notif/notifier" + "github.com/crazy-max/diun/internal/notif/rocketchat" "github.com/crazy-max/diun/internal/notif/slack" "github.com/crazy-max/diun/internal/notif/telegram" "github.com/crazy-max/diun/internal/notif/webhook" @@ -27,9 +28,15 @@ func New(config model.Notif, app model.App, userAgent string) (*Client, error) { } // Add notifiers + if config.Gotify.Enable { + c.notifiers = append(c.notifiers, gotify.New(config.Gotify, app, userAgent)) + } if config.Mail.Enable { c.notifiers = append(c.notifiers, mail.New(config.Mail, app)) } + if config.RocketChat.Enable { + c.notifiers = append(c.notifiers, rocketchat.New(config.RocketChat, app, userAgent)) + } if config.Slack.Enable { c.notifiers = append(c.notifiers, slack.New(config.Slack, app)) } @@ -39,9 +46,6 @@ func New(config model.Notif, app model.App, userAgent string) (*Client, error) { if config.Webhook.Enable { c.notifiers = append(c.notifiers, webhook.New(config.Webhook, app, userAgent)) } - if config.Gotify.Enable { - c.notifiers = append(c.notifiers, gotify.New(config.Gotify, app, userAgent)) - } log.Debug().Msgf("%d notifier(s) created", len(c.notifiers)) return c, nil diff --git a/internal/notif/rocketchat/client.go b/internal/notif/rocketchat/client.go new file mode 100644 index 00000000..c6c833c2 --- /dev/null +++ b/internal/notif/rocketchat/client.go @@ -0,0 +1,136 @@ +package rocketchat + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" + "path" + "strconv" + "text/template" + "time" + + "github.com/crazy-max/diun/internal/model" + "github.com/crazy-max/diun/internal/notif/notifier" +) + +// Client represents an active rocketchat notification object +type Client struct { + *notifier.Notifier + cfg model.NotifRocketChat + app model.App + userAgent string +} + +// New creates a new rocketchat notification instance +func New(config model.NotifRocketChat, app model.App, userAgent string) notifier.Notifier { + return notifier.Notifier{ + Handler: &Client{ + cfg: config, + app: app, + userAgent: userAgent, + }, + } +} + +// Name returns notifier's name +func (c *Client) Name() string { + return "rocketchat" +} + +// Send creates and sends a rocketchat notification with an entry +// https://rocket.chat/docs/developer-guides/rest-api/chat/postmessage/ +func (c *Client) Send(entry model.NotifEntry) error { + hc := http.Client{ + Timeout: time.Duration(c.cfg.Timeout) * time.Second, + } + + title := fmt.Sprintf("Image update for %s", entry.Image.String()) + if entry.Status == model.ImageStatusNew { + title = fmt.Sprintf("New image %s has been added", entry.Image.String()) + } + + var textBuf bytes.Buffer + textTpl := template.Must(template.New("rocketchat").Parse(`Docker 🐳 tag {{ .Image.Domain }}/{{ .Image.Path }}:{{ .Image.Tag }} which you subscribed to through {{ .Provider }} provider has been {{ if (eq .Status "new") }}newly added{{ else }}updated{{ end }}.`)) + if err := textTpl.Execute(&textBuf, entry); err != nil { + return err + } + + data := Message{ + Alias: c.app.Name, + Avatar: "https://raw.githubusercontent.com/crazy-max/diun/master/.res/diun.png", + Channel: c.cfg.Channel, + Text: title, + Attachments: []Attachment{ + { + Text: textBuf.String(), + Ts: json.Number(strconv.FormatInt(time.Now().Unix(), 10)), + Fields: []AttachmentField{ + { + Title: "Provider", + Value: entry.Provider, + Short: false, + }, + { + Title: "Created", + Value: entry.Manifest.Created.Format("Jan 02, 2006 15:04:05 UTC"), + Short: false, + }, + { + Title: "Digest", + Value: entry.Manifest.Digest.String(), + Short: false, + }, + { + Title: "Platform", + Value: fmt.Sprintf("%s/%s", entry.Manifest.Os, entry.Manifest.Architecture), + Short: false, + }, + }, + }, + }, + } + + dataBuf := new(bytes.Buffer) + if err := json.NewEncoder(dataBuf).Encode(data); err != nil { + return err + } + + u, err := url.Parse(c.cfg.Endpoint) + if err != nil { + return err + } + u.Path = path.Join(u.Path, "api/v1/chat.postMessage") + + req, err := http.NewRequest("POST", u.String(), dataBuf) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", c.userAgent) + req.Header.Add("X-User-Id", c.cfg.UserID) + req.Header.Add("X-Auth-Token", c.cfg.Token) + + resp, err := hc.Do(req) + if err != nil { + return err + } + + var respBody struct { + Success bool `json:"success"` + Error string `json:"error,omitempty"` + ErrorType string `json:"errorType,omitempty"` + } + err = json.NewDecoder(resp.Body).Decode(&respBody) + if err == nil { + return err + } + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("HTTP error %d: %s", resp.StatusCode, respBody.ErrorType) + } + + return nil +} diff --git a/internal/notif/rocketchat/model.go b/internal/notif/rocketchat/model.go new file mode 100644 index 00000000..89724f46 --- /dev/null +++ b/internal/notif/rocketchat/model.go @@ -0,0 +1,41 @@ +package rocketchat + +import "encoding/json" + +// Message contains all the information for a message +type Message struct { + Alias string `json:"alias,omitempty"` + Avatar string `json:"avatar,omitempty"` + Channel string `json:"channel,omitempty"` + Emoji string `json:"emoji,omitempty"` + RoomID string `json:"roomId,omitempty"` + Text string `json:"text,omitempty"` + Attachments []Attachment `json:"attachments,omitempty"` +} + +// Attachment contains all the information for an attachment +type Attachment struct { + AudioURL string `json:"audio_url,omitempty"` + AuthorIcon string `json:"author_icon,omitempty"` + AuthorLink string `json:"author_link,omitempty"` + AuthorName string `json:"author_name,omitempty"` + Collapsed bool `json:"collapsed,omitempty"` + Color bool `json:"color,omitempty"` + Fields []AttachmentField `json:"fields,omitempty"` + ImageURL string `json:"image_url,omitempty"` + MessageLink string `json:"message_link,omitempty"` + Text string `json:"text"` + ThumbURL string `json:"thumb_url,omitempty"` + Title string `json:"title,omitempty"` + TitleLink string `json:"title_link,omitempty"` + Ts json.Number `json:"ts,omitempty"` + VideoURL string `json:"video_url,omitempty"` +} + +// AttachmentField contains information for an attachment field +// An Attachment can contain multiple of these +type AttachmentField struct { + Title string `json:"title"` + Value string `json:"value"` + Short bool `json:"short"` +} diff --git a/internal/notif/slack/slack.go b/internal/notif/slack/slack.go index 8c9eddf8..9d3a5986 100644 --- a/internal/notif/slack/slack.go +++ b/internal/notif/slack/slack.go @@ -49,37 +49,39 @@ func (c *Client) Send(entry model.NotifEntry) error { } return slack.PostWebhook(c.cfg.WebhookURL, &slack.WebhookMessage{ - Attachments: []slack.Attachment{slack.Attachment{ - Color: color, - AuthorName: "Diun", - AuthorSubname: "github.com/crazy-max/diun", - AuthorLink: "https://github.com/crazy-max/diun", - AuthorIcon: "https://raw.githubusercontent.com/crazy-max/diun/master/.res/diun.png", - Text: textBuf.String(), - Footer: fmt.Sprintf("%s © %d %s %s", c.app.Author, time.Now().Year(), c.app.Name, c.app.Version), - Fields: []slack.AttachmentField{ - { - Title: "Provider", - Value: entry.Provider, - Short: false, - }, - { - Title: "Created", - Value: entry.Manifest.Created.Format("Jan 02, 2006 15:04:05 UTC"), - Short: false, - }, - { - Title: "Digest", - Value: entry.Manifest.Digest.String(), - Short: false, - }, - { - Title: "Platform", - Value: fmt.Sprintf("%s/%s", entry.Manifest.Os, entry.Manifest.Architecture), - Short: false, + Attachments: []slack.Attachment{ + { + Color: color, + AuthorName: "Diun", + AuthorSubname: "github.com/crazy-max/diun", + AuthorLink: "https://github.com/crazy-max/diun", + AuthorIcon: "https://raw.githubusercontent.com/crazy-max/diun/master/.res/diun.png", + Text: textBuf.String(), + Footer: fmt.Sprintf("%s © %d %s %s", c.app.Author, time.Now().Year(), c.app.Name, c.app.Version), + Fields: []slack.AttachmentField{ + { + Title: "Provider", + Value: entry.Provider, + Short: false, + }, + { + Title: "Created", + Value: entry.Manifest.Created.Format("Jan 02, 2006 15:04:05 UTC"), + Short: false, + }, + { + Title: "Digest", + Value: entry.Manifest.Digest.String(), + Short: false, + }, + { + Title: "Platform", + Value: fmt.Sprintf("%s/%s", entry.Manifest.Os, entry.Manifest.Architecture), + Short: false, + }, }, + Ts: json.Number(strconv.FormatInt(time.Now().Unix(), 10)), }, - Ts: json.Number(strconv.FormatInt(time.Now().Unix(), 10)), - }}, + }, }) } diff --git a/pkg/registry/image.go b/pkg/registry/image.go index d16dfd00..c0e1c4b0 100644 --- a/pkg/registry/image.go +++ b/pkg/registry/image.go @@ -1,4 +1,3 @@ -// Source: https://github.com/genuinetools/reg/blob/f3a9b00ec86f334702381edf842f03b3a9243a0a/registry/image.go package registry import ( @@ -8,6 +7,8 @@ import ( digest "github.com/opencontainers/go-digest" ) +// Source: https://github.com/genuinetools/reg/blob/f3a9b00ec86f334702381edf842f03b3a9243a0a/registry/image.go + // Image holds information about an image. type Image struct { Domain string diff --git a/pkg/registry/manifest.go b/pkg/registry/manifest.go index 5f9d1a1c..1da417a2 100644 --- a/pkg/registry/manifest.go +++ b/pkg/registry/manifest.go @@ -7,6 +7,7 @@ import ( "github.com/opencontainers/go-digest" ) +// Manifest is the Docker image manifest information type Manifest struct { Name string Tag string diff --git a/pkg/registry/tags.go b/pkg/registry/tags.go index 23df2043..e0ae1880 100644 --- a/pkg/registry/tags.go +++ b/pkg/registry/tags.go @@ -5,6 +5,7 @@ import ( "github.com/crazy-max/diun/pkg/utl" ) +// Tags holds information about image tags. type Tags struct { List []string NotIncluded int @@ -12,6 +13,7 @@ type Tags struct { Total int } +// TagsOptions holds docker tags image options type TagsOptions struct { Image Image Max int