0
0
Fork 0
mirror of https://github.com/crazy-max/diun.git synced 2025-04-14 15:18:32 +00:00

Add Rocket.Chat notifier ()

This commit is contained in:
CrazyMax 2020-03-01 17:09:22 +01:00 committed by GitHub
parent 8a49f41fdd
commit 08cd3d90b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 321 additions and 82 deletions

BIN
.res/notif-rocketchat.png Normal file

Binary file not shown.

After

(image error) Size: 11 KiB

View file

@ -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"

View file

@ -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.

View file

@ -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)

View file

@ -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,
},
},
}

View file

@ -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:

View file

@ -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": {

View file

@ -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,

View file

@ -5,6 +5,6 @@ type Flags struct {
Cfgfile string
Timezone string
LogLevel string
LogJson bool
LogJSON bool
LogCaller bool
}

View file

@ -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"`
}

View file

@ -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

View file

@ -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
}

View file

@ -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"`
}

View file

@ -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)),
}},
},
})
}

View file

@ -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

View file

@ -7,6 +7,7 @@ import (
"github.com/opencontainers/go-digest"
)
// Manifest is the Docker image manifest information
type Manifest struct {
Name string
Tag string

View file

@ -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