mirror of
https://github.com/crazy-max/diun.git
synced 2026-01-01 02:27:26 +01:00
Add Slack notifier (#8)
This commit is contained in:
BIN
.res/notif-slack.png
Normal file
BIN
.res/notif-slack.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## 2.1.0 (2019/12/17)
|
||||
|
||||
* Add Slack notifier (#8)
|
||||
|
||||
## 2.0.0 (2019/12/14)
|
||||
|
||||
* Include provider in notifications
|
||||
|
||||
@@ -21,6 +21,9 @@ notif:
|
||||
password:
|
||||
from:
|
||||
to:
|
||||
slack:
|
||||
enable: false
|
||||
webhook_url: https://hooks.slack.com/services/ABCD12EFG/HIJK34LMN/01234567890abcdefghij
|
||||
webhook:
|
||||
enable: false
|
||||
endpoint: http://webhook.foo.com/sd54qad89azd5a
|
||||
@@ -107,6 +110,10 @@ providers:
|
||||
* `from`: Sender email address. **required**
|
||||
* `to`: Recipient email address. **required**
|
||||
|
||||
* `slack`
|
||||
* `enable`: Enable slack notification (default: `false`).
|
||||
* `webhook_url`: Slack [incoming webhook URL](https://api.slack.com/messaging/webhooks). **required**
|
||||
|
||||
* `webhook`
|
||||
* `enable`: Enable webhook notification (default: `false`).
|
||||
* `endpoint`: URL of the HTTP request. **required**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Notifications
|
||||
|
||||
* [Mail](#mail)
|
||||
* [Slack](#slack)
|
||||
* [Webhook](#webhook)
|
||||
|
||||
## Mail
|
||||
@@ -9,6 +10,12 @@ Here is an email sample if you add `mail` notification:
|
||||
|
||||

|
||||
|
||||
## Slack
|
||||
|
||||
You can send notifications to your slack channel using an [incoming webhook URL](https://api.slack.com/messaging/webhooks):
|
||||
|
||||

|
||||
|
||||
## Webhook
|
||||
|
||||
If you choose `webhook` notification, a HTTP request is sent with a JSON format response that looks like:
|
||||
|
||||
1
go.mod
1
go.mod
@@ -24,6 +24,7 @@ require (
|
||||
github.com/imdario/mergo v0.3.8
|
||||
github.com/matcornic/hermes/v2 v2.0.2
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/nlopes/slack v0.6.0
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1
|
||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||
github.com/panjf2000/ants/v2 v2.2.2
|
||||
|
||||
4
go.sum
4
go.sum
@@ -92,6 +92,8 @@ github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
|
||||
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/hako/durafmt v0.0.0-20190612201238-650ed9f29a84 h1:RvcDqcKLua4b/jtXez7ZVe9s6Iq5N6ujVevqY4FBQmM=
|
||||
github.com/hako/durafmt v0.0.0-20190612201238-650ed9f29a84/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE=
|
||||
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@@ -136,6 +138,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nlopes/slack v0.6.0 h1:jt0jxVQGhssx1Ib7naAOZEZcGdtIhTzkP0nopK0AsRA=
|
||||
github.com/nlopes/slack v0.6.0/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk=
|
||||
github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
|
||||
@@ -48,14 +48,17 @@ func Load(flags model.Flags, version string) (*Config, error) {
|
||||
Schedule: "0 * * * *",
|
||||
},
|
||||
Notif: model.Notif{
|
||||
Mail: model.Mail{
|
||||
Mail: model.NotifMail{
|
||||
Enable: false,
|
||||
Host: "localhost",
|
||||
Port: 25,
|
||||
SSL: false,
|
||||
InsecureSkipVerify: false,
|
||||
},
|
||||
Webhook: model.Webhook{
|
||||
Slack: model.NotifSlack{
|
||||
Enable: false,
|
||||
},
|
||||
Webhook: model.NotifWebhook{
|
||||
Enable: false,
|
||||
Method: "GET",
|
||||
Timeout: 10,
|
||||
|
||||
@@ -18,6 +18,9 @@ notif:
|
||||
password_file:
|
||||
from:
|
||||
to:
|
||||
slack:
|
||||
enable: false
|
||||
webhook_url: https://hooks.slack.com/services/ABCD12EFG/HIJK34LMN/01234567890abcdefghij
|
||||
webhook:
|
||||
enable: false
|
||||
endpoint: http://webhook.foo.com/sd54qad89azd5a
|
||||
|
||||
@@ -52,14 +52,18 @@ func TestLoad(t *testing.T) {
|
||||
Schedule: "*/30 * * * *",
|
||||
},
|
||||
Notif: model.Notif{
|
||||
Mail: model.Mail{
|
||||
Mail: model.NotifMail{
|
||||
Enable: false,
|
||||
Host: "localhost",
|
||||
Port: 25,
|
||||
SSL: false,
|
||||
InsecureSkipVerify: false,
|
||||
},
|
||||
Webhook: model.Webhook{
|
||||
Slack: model.NotifSlack{
|
||||
Enable: false,
|
||||
WebhookURL: "https://hooks.slack.com/services/ABCD12EFG/HIJK34LMN/01234567890abcdefghij",
|
||||
},
|
||||
Webhook: model.NotifWebhook{
|
||||
Enable: false,
|
||||
Endpoint: "http://webhook.foo.com/sd54qad89azd5a",
|
||||
Method: "GET",
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package model
|
||||
|
||||
// Mail holds mail notification configuration details
|
||||
type Mail struct {
|
||||
Enable bool `yaml:"enable,omitempty"`
|
||||
Host string `yaml:"host,omitempty"`
|
||||
Port int `yaml:"port,omitempty"`
|
||||
SSL bool `yaml:"ssl,omitempty"`
|
||||
InsecureSkipVerify bool `yaml:"insecure_skip_verify,omitempty"`
|
||||
Username string `yaml:"username,omitempty"`
|
||||
UsernameFile string `yaml:"username_file,omitempty"`
|
||||
Password string `yaml:"password,omitempty"`
|
||||
PasswordFile string `yaml:"password_file,omitempty"`
|
||||
From string `yaml:"from,omitempty"`
|
||||
To string `yaml:"to,omitempty"`
|
||||
}
|
||||
@@ -5,12 +5,6 @@ import (
|
||||
"github.com/crazy-max/diun/pkg/docker/registry"
|
||||
)
|
||||
|
||||
// Notif holds data necessary for notification configuration
|
||||
type Notif struct {
|
||||
Mail Mail `yaml:"mail,omitempty"`
|
||||
Webhook Webhook `yaml:"webhook,omitempty"`
|
||||
}
|
||||
|
||||
// NotifEntry represents a notification entry
|
||||
type NotifEntry struct {
|
||||
Status ImageStatus `json:"status,omitempty"`
|
||||
@@ -18,3 +12,40 @@ type NotifEntry struct {
|
||||
Image registry.Image `json:"image,omitempty"`
|
||||
Manifest docker.Manifest `json:"manifest,omitempty"`
|
||||
}
|
||||
|
||||
// Notif holds data necessary for notification configuration
|
||||
type Notif struct {
|
||||
Mail NotifMail `yaml:"mail,omitempty"`
|
||||
Slack NotifSlack `yaml:"slack,omitempty"`
|
||||
Webhook NotifWebhook `yaml:"webhook,omitempty"`
|
||||
}
|
||||
|
||||
// NotifMail holds mail notification configuration details
|
||||
type NotifMail struct {
|
||||
Enable bool `yaml:"enable,omitempty"`
|
||||
Host string `yaml:"host,omitempty"`
|
||||
Port int `yaml:"port,omitempty"`
|
||||
SSL bool `yaml:"ssl,omitempty"`
|
||||
InsecureSkipVerify bool `yaml:"insecure_skip_verify,omitempty"`
|
||||
Username string `yaml:"username,omitempty"`
|
||||
UsernameFile string `yaml:"username_file,omitempty"`
|
||||
Password string `yaml:"password,omitempty"`
|
||||
PasswordFile string `yaml:"password_file,omitempty"`
|
||||
From string `yaml:"from,omitempty"`
|
||||
To string `yaml:"to,omitempty"`
|
||||
}
|
||||
|
||||
// NotifSlack holds slack notification configuration details
|
||||
type NotifSlack struct {
|
||||
Enable bool `yaml:"enable,omitempty"`
|
||||
WebhookURL string `yaml:"webhook_url,omitempty"`
|
||||
}
|
||||
|
||||
// NotifWebhook holds webhook notification configuration details
|
||||
type NotifWebhook struct {
|
||||
Enable bool `yaml:"enable,omitempty"`
|
||||
Endpoint string `yaml:"endpoint,omitempty"`
|
||||
Method string `yaml:"method,omitempty"`
|
||||
Headers map[string]string `yaml:"headers,omitempty"`
|
||||
Timeout int `yaml:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package model
|
||||
|
||||
// Webhook holds webhook notification configuration details
|
||||
type Webhook struct {
|
||||
Enable bool `yaml:"enable,omitempty"`
|
||||
Endpoint string `yaml:"endpoint,omitempty"`
|
||||
Method string `yaml:"method,omitempty"`
|
||||
Headers map[string]string `yaml:"headers,omitempty"`
|
||||
Timeout int `yaml:"timeout,omitempty"`
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/crazy-max/diun/internal/model"
|
||||
"github.com/crazy-max/diun/internal/notif/mail"
|
||||
"github.com/crazy-max/diun/internal/notif/notifier"
|
||||
"github.com/crazy-max/diun/internal/notif/slack"
|
||||
"github.com/crazy-max/diun/internal/notif/webhook"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
@@ -27,6 +28,9 @@ func New(config model.Notif, app model.App) (*Client, error) {
|
||||
if config.Mail.Enable {
|
||||
c.notifiers = append(c.notifiers, mail.New(config.Mail, app))
|
||||
}
|
||||
if config.Slack.Enable {
|
||||
c.notifiers = append(c.notifiers, slack.New(config.Slack, app))
|
||||
}
|
||||
if config.Webhook.Enable {
|
||||
c.notifiers = append(c.notifiers, webhook.New(config.Webhook, app))
|
||||
}
|
||||
|
||||
@@ -18,12 +18,12 @@ import (
|
||||
// Client represents an active mail notification object
|
||||
type Client struct {
|
||||
*notifier.Notifier
|
||||
cfg model.Mail
|
||||
cfg model.NotifMail
|
||||
app model.App
|
||||
}
|
||||
|
||||
// New creates a new mail notification instance
|
||||
func New(config model.Mail, app model.App) notifier.Notifier {
|
||||
func New(config model.NotifMail, app model.App) notifier.Notifier {
|
||||
return notifier.Notifier{
|
||||
Handler: &Client{
|
||||
cfg: config,
|
||||
@@ -65,7 +65,7 @@ func (c *Client) Send(entry model.NotifEntry) error {
|
||||
|
||||
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 }}.
|
||||
|
||||
This image has been {{ if (eq .Status "new") }}created{{ else }}updated{{ end }} at <code>{{ .Manifest.Created }}</code> with digest <code>{{ .Manifest.Digest }}</code> for <code>{{ .Manifest.Os }}/{{ .Manifest.Architecture }}</code> platform.
|
||||
This image has been {{ if (eq .Status "new") }}created{{ else }}updated{{ end }} at <code>{{ .Manifest.Created.Format "Jan 02, 2006 15:04:05 UTC" }}</code> with digest <code>{{ .Manifest.Digest }}</code> for <code>{{ .Manifest.Os }}/{{ .Manifest.Architecture }}</code> platform.
|
||||
|
||||
Need help, or have questions? Go to https://github.com/crazy-max/diun and leave an issue.
|
||||
|
||||
|
||||
85
internal/notif/slack/slack.go
Normal file
85
internal/notif/slack/slack.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/crazy-max/diun/internal/model"
|
||||
"github.com/crazy-max/diun/internal/notif/notifier"
|
||||
"github.com/nlopes/slack"
|
||||
)
|
||||
|
||||
// Client represents an active slack notification object
|
||||
type Client struct {
|
||||
*notifier.Notifier
|
||||
cfg model.NotifSlack
|
||||
app model.App
|
||||
}
|
||||
|
||||
// New creates a new slack notification instance
|
||||
func New(config model.NotifSlack, app model.App) notifier.Notifier {
|
||||
return notifier.Notifier{
|
||||
Handler: &Client{
|
||||
cfg: config,
|
||||
app: app,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns notifier's name
|
||||
func (c *Client) Name() string {
|
||||
return "slack"
|
||||
}
|
||||
|
||||
// Send creates and sends a webhook notification with an entry
|
||||
func (c *Client) Send(entry model.NotifEntry) error {
|
||||
var textBuf bytes.Buffer
|
||||
textTpl := template.Must(template.New("text").Parse("<!channel> Docker tag `{{ .Image.Domain }}/{{ .Image.Path }}:{{ .Image.Tag }}` {{ if (eq .Status \"new\") }}newly added{{ else }}updated{{ end }}."))
|
||||
if err := textTpl.Execute(&textBuf, entry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
color := "#4caf50"
|
||||
if entry.Status == model.ImageStatusUpdate {
|
||||
color = "#0054ca"
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
Ts: json.Number(strconv.FormatInt(time.Now().Unix(), 10)),
|
||||
}},
|
||||
})
|
||||
}
|
||||
@@ -15,12 +15,12 @@ import (
|
||||
// Client represents an active webhook notification object
|
||||
type Client struct {
|
||||
*notifier.Notifier
|
||||
cfg model.Webhook
|
||||
cfg model.NotifWebhook
|
||||
app model.App
|
||||
}
|
||||
|
||||
// New creates a new webhook notification instance
|
||||
func New(config model.Webhook, app model.App) notifier.Notifier {
|
||||
func New(config model.NotifWebhook, app model.App) notifier.Notifier {
|
||||
return notifier.Notifier{
|
||||
Handler: &Client{
|
||||
cfg: config,
|
||||
|
||||
Reference in New Issue
Block a user