diff --git a/internal/msg/client.go b/internal/msg/client.go new file mode 100644 index 00000000..0cee2d00 --- /dev/null +++ b/internal/msg/client.go @@ -0,0 +1,127 @@ +package msg + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" + "text/template" + "time" + + "github.com/crazy-max/diun/v4/internal/model" + "github.com/microcosm-cc/bluemonday" + "github.com/opencontainers/go-digest" + "github.com/russross/blackfriday/v2" +) + +// Client represents an active msg object +type Client struct { + opts Options +} + +// Options holds msg client object options +type Options struct { + Meta model.Meta + Entry model.NotifEntry + TplFuncs template.FuncMap +} + +const defaultTpl = `Docker tag {{ if .Entry.Image.HubLink }}[**{{ .Entry.Image }}**]({{ .Entry.Image.HubLink }}){{ else }}**{{ .Entry.Image }}**{{ end }} +which you subscribed to through {{ .Entry.Provider }} provider has been {{ if (eq .Entry.Status "new") }}newly added{{ else }}updated{{ end }} +on {{ .Meta.Hostname }}.` + +// New initializes a new msg client +func New(opts Options) (*Client, error) { + return &Client{ + opts, + }, nil +} + +// RenderMarkdown returns a notification message as markdown +func (c *Client) RenderMarkdown() (title string, text []byte, err error) { + return c.RenderHTMLTemplate(strings.ReplaceAll(defaultTpl, "\n", " ")) +} + +// RenderMarkdownTemplate returns a notification message as markdown with a custom template +func (c *Client) RenderMarkdownTemplate(tpl string) (title string, text []byte, err error) { + title = fmt.Sprintf("Image update for %s", c.opts.Entry.Image.String()) + if c.opts.Entry.Status == model.ImageStatusNew { + title = fmt.Sprintf("New image %s has been added", c.opts.Entry.Image.String()) + } + + var msgBuf bytes.Buffer + msgTpl := template.Must(template.New("notif").Funcs(c.opts.TplFuncs).Parse(tpl)) + err = msgTpl.Execute(&msgBuf, struct { + Meta model.Meta + Entry model.NotifEntry + }{ + Meta: c.opts.Meta, + Entry: c.opts.Entry, + }) + + text = msgBuf.Bytes() + return +} + +// RenderHTML returns a notification message as html +func (c *Client) RenderHTML() (title string, text []byte, err error) { + return c.RenderHTMLTemplate(strings.ReplaceAll(defaultTpl, "\n", " ")) +} + +// RenderHTMLTemplate returns a notification message as html with a custom template +func (c *Client) RenderHTMLTemplate(tpl string) (title string, text []byte, err error) { + title, text, err = c.RenderMarkdownTemplate(tpl) + if err != nil { + return + } + + text = []byte(bluemonday.UGCPolicy().Sanitize( + // Dirty way to remove wrapped
and newline + // https://github.com/russross/blackfriday/issues/237 + strings.TrimRight(strings.TrimLeft(strings.TrimSpace(string(blackfriday.Run(text))), ""), "
"), + )) + return +} + +// RenderJSON returns a notification message as JSON +func (c *Client) RenderJSON() ([]byte, error) { + return json.Marshal(struct { + Version string `json:"diun_version"` + Hostname string `json:"hostname"` + Status string `json:"status"` + Provider string `json:"provider"` + Image string `json:"image"` + HubLink string `json:"hub_link"` + MIMEType string `json:"mime_type"` + Digest digest.Digest `json:"digest"` + Created *time.Time `json:"created"` + Platform string `json:"platform"` + }{ + Version: c.opts.Meta.Version, + Hostname: c.opts.Meta.Hostname, + Status: string(c.opts.Entry.Status), + Provider: c.opts.Entry.Provider, + Image: c.opts.Entry.Image.String(), + HubLink: c.opts.Entry.Image.HubLink, + MIMEType: c.opts.Entry.Manifest.MIMEType, + Digest: c.opts.Entry.Manifest.Digest, + Created: c.opts.Entry.Manifest.Created, + Platform: c.opts.Entry.Manifest.Platform, + }) +} + +// RenderEnv returns a notification message as environment variables +func (c *Client) RenderEnv() []string { + return []string{ + fmt.Sprintf("DIUN_VERSION=%s", c.opts.Meta.Version), + fmt.Sprintf("DIUN_HOSTNAME=%s", c.opts.Meta.Hostname), + fmt.Sprintf("DIUN_ENTRY_STATUS=%s", string(c.opts.Entry.Status)), + fmt.Sprintf("DIUN_ENTRY_PROVIDER=%s", c.opts.Entry.Provider), + fmt.Sprintf("DIUN_ENTRY_IMAGE=%s", c.opts.Entry.Image.String()), + fmt.Sprintf("DIUN_ENTRY_HUBLINK=%s", c.opts.Entry.Image.HubLink), + fmt.Sprintf("DIUN_ENTRY_MIMETYPE=%s", c.opts.Entry.Manifest.MIMEType), + fmt.Sprintf("DIUN_ENTRY_DIGEST=%s", c.opts.Entry.Manifest.Digest), + fmt.Sprintf("DIUN_ENTRY_CREATED=%s", c.opts.Entry.Manifest.Created), + fmt.Sprintf("DIUN_ENTRY_PLATFORM=%s", c.opts.Entry.Manifest.Platform), + } +} diff --git a/internal/notif/amqp/client.go b/internal/notif/amqp/client.go index fa47c43d..f2a6f993 100644 --- a/internal/notif/amqp/client.go +++ b/internal/notif/amqp/client.go @@ -1,14 +1,12 @@ package amqp import ( - "encoding/json" "fmt" - "time" "github.com/crazy-max/diun/v4/internal/model" + "github.com/crazy-max/diun/v4/internal/msg" "github.com/crazy-max/diun/v4/internal/notif/notifier" "github.com/crazy-max/diun/v4/pkg/utl" - "github.com/opencontainers/go-digest" "github.com/streadway/amqp" ) @@ -70,33 +68,19 @@ func (c *Client) Send(entry model.NotifEntry) error { return err } - body, err := json.Marshal(struct { - Version string `json:"diun_version"` - Hostname string `json:"hostname"` - Status string `json:"status"` - Provider string `json:"provider"` - Image string `json:"image"` - HubLink string `json:"hub_link"` - MIMEType string `json:"mime_type"` - Digest digest.Digest `json:"digest"` - Created *time.Time `json:"created"` - Platform string `json:"platform"` - }{ - Version: c.meta.Version, - Hostname: c.meta.Hostname, - Status: string(entry.Status), - Provider: entry.Provider, - Image: entry.Image.String(), - HubLink: entry.Image.HubLink, - MIMEType: entry.Manifest.MIMEType, - Digest: entry.Manifest.Digest, - Created: entry.Manifest.Created, - Platform: entry.Manifest.Platform, + message, err := msg.New(msg.Options{ + Meta: c.meta, + Entry: entry, }) if err != nil { return err } + body, err := message.RenderJSON() + if err != nil { + return err + } + return ch.Publish( c.cfg.Exchange, q.Name, diff --git a/internal/notif/discord/client.go b/internal/notif/discord/client.go index 9b15be15..b7bb4fa0 100644 --- a/internal/notif/discord/client.go +++ b/internal/notif/discord/client.go @@ -6,10 +6,10 @@ import ( "fmt" "net/http" "net/url" - "text/template" "time" "github.com/crazy-max/diun/v4/internal/model" + "github.com/crazy-max/diun/v4/internal/msg" "github.com/crazy-max/diun/v4/internal/notif/notifier" ) @@ -44,33 +44,25 @@ func (c *Client) Send(entry model.NotifEntry) error { Timeout: *c.cfg.Timeout, } + message, err := msg.New(msg.Options{ + Meta: c.meta, + Entry: entry, + }) + if err != nil { + return err + } + + title, text, err := message.RenderMarkdown() + if err != nil { + return err + } + if len(c.cfg.Mentions) > 0 { for _, mention := range c.cfg.Mentions { content.WriteString(fmt.Sprintf("%s ", mention)) } } - if entry.Status == model.ImageStatusNew { - content.WriteString(fmt.Sprintf("New image %s has been added", entry.Image.String())) - } else { - content.WriteString(fmt.Sprintf("Image update for %s", entry.Image.String())) - } - - tagTpl := "**{{ .Entry.Image.Domain }}/{{ .Entry.Image.Path }}:{{ .Entry.Image.Tag }}**" - if len(entry.Image.HubLink) > 0 { - tagTpl = "[**{{ .Entry.Image.Domain }}/{{ .Entry.Image.Path }}:{{ .Entry.Image.Tag }}**]({{ .Entry.Image.HubLink }})" - } - - var textBuf bytes.Buffer - textTpl := template.Must(template.New("discord").Parse(fmt.Sprintf(`Docker tag %s which you subscribed to through **{{ .Entry.Provider }}** provider has been {{ if (eq .Entry.Status "new") }}newly added{{ else }}updated{{ end }} on **{{ .Meta.Hostname }}**.`, tagTpl))) - if err := textTpl.Execute(&textBuf, struct { - Meta model.Meta - Entry model.NotifEntry - }{ - Meta: c.meta, - Entry: entry, - }); err != nil { - return err - } + content.WriteString(title) fields := []EmbedField{ { @@ -108,7 +100,7 @@ func (c *Client) Send(entry model.NotifEntry) error { AvatarURL: c.meta.Logo, Embeds: []Embed{ { - Description: textBuf.String(), + Description: string(text), Footer: EmbedFooter{ Text: fmt.Sprintf("%s © %d %s %s", c.meta.Author, time.Now().Year(), c.meta.Name, c.meta.Version), IconURL: c.meta.Logo, diff --git a/internal/notif/gotify/client.go b/internal/notif/gotify/client.go index dc624779..cb6905e0 100644 --- a/internal/notif/gotify/client.go +++ b/internal/notif/gotify/client.go @@ -8,9 +8,9 @@ import ( "net/url" "path" "strconv" - "text/template" "github.com/crazy-max/diun/v4/internal/model" + "github.com/crazy-max/diun/v4/internal/msg" "github.com/crazy-max/diun/v4/internal/notif/notifier" ) @@ -42,35 +42,26 @@ func (c *Client) Send(entry model.NotifEntry) error { Timeout: *c.cfg.Timeout, } - 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()) - } - - tagTpl := "`{{ .Entry.Image.Domain }}/{{ .Entry.Image.Path }}:{{ .Entry.Image.Tag }}`" - if len(entry.Image.HubLink) > 0 { - tagTpl = "[`{{ .Entry.Image.Domain }}/{{ .Entry.Image.Path }}:{{ .Entry.Image.Tag }}`]({{ .Entry.Image.HubLink }})" - } - - var msgBuf bytes.Buffer - msgTpl := template.Must(template.New("gotify").Parse(fmt.Sprintf("Docker tag %s which you subscribed to through {{ .Entry.Provider }} provider has been {{ if (eq .Entry.Status \"new\") }}newly added{{ else }}updated{{ end }} on {{ .Meta.Hostname }}.", tagTpl))) - if err := msgTpl.Execute(&msgBuf, struct { - Meta model.Meta - Entry model.NotifEntry - }{ + message, err := msg.New(msg.Options{ Meta: c.meta, Entry: entry, - }); err != nil { + }) + if err != nil { return err } - var body, err = json.Marshal(struct { + title, text, err := message.RenderMarkdown() + if err != nil { + return err + } + + body, err := json.Marshal(struct { Message string `json:"message"` Title string `json:"title"` Priority int `json:"priority"` Extras map[string]interface{} `json:"extras"` }{ - Message: msgBuf.String(), + Message: string(text), Title: title, Priority: c.cfg.Priority, Extras: map[string]interface{}{ diff --git a/internal/notif/mail/client.go b/internal/notif/mail/client.go index cb57889d..506d8b49 100644 --- a/internal/notif/mail/client.go +++ b/internal/notif/mail/client.go @@ -1,13 +1,12 @@ package mail import ( - "bytes" "crypto/tls" "fmt" - "text/template" "time" "github.com/crazy-max/diun/v4/internal/model" + "github.com/crazy-max/diun/v4/internal/msg" "github.com/crazy-max/diun/v4/internal/notif/notifier" "github.com/crazy-max/diun/v4/pkg/utl" "github.com/go-gomail/gomail" @@ -22,6 +21,16 @@ type Client struct { meta model.Meta } +const customTpl = `Docker tag {{ if .Entry.Image.HubLink }}[**{{ .Entry.Image }}**]({{ .Entry.Image.HubLink }}){{ else }}**{{ .Entry.Image }}**{{ end }} +which you subscribed to through {{ .Entry.Provider }} provider has been {{ if (eq .Entry.Status "new") }}newly added{{ else }}updated{{ end }} +on {{ .Meta.Hostname }}. + +This image has been {{ if (eq .Entry.Status "new") }}created{{ else }}updated{{ end }} at +{{ .Entry.Manifest.Created.Format "Jan 02, 2006 15:04:05 UTC" }} with digest {{ .Entry.Manifest.Digest }}
+for {{ .Entry.Manifest.Platform }} platform.
+
+Need help, or have questions? Go to {{ .Meta.URL }} and leave an issue.`
+
// New creates a new mail notification instance
func New(config *model.NotifMail, meta model.Meta) notifier.Notifier {
return notifier.Notifier{
@@ -53,43 +62,24 @@ func (c *Client) Send(entry model.NotifEntry) error {
},
}
- // Subject
- subject := fmt.Sprintf("Image update for %s", entry.Image.String())
- if entry.Status == model.ImageStatusNew {
- subject = fmt.Sprintf("New image %s has been added", entry.Image.String())
- }
-
- tagTpl := "**{{ .Entry.Image.Domain }}/{{ .Entry.Image.Path }}:{{ .Entry.Image.Tag }}**"
- if len(entry.Image.HubLink) > 0 {
- tagTpl = "[**{{ .Entry.Image.Domain }}/{{ .Entry.Image.Path }}:{{ .Entry.Image.Tag }}**]({{ .Entry.Image.HubLink }})"
- }
-
- // Body
- var emailBuf bytes.Buffer
- emailTpl := template.Must(template.New("email").Parse(fmt.Sprintf(`
-
-Docker tag %s which you subscribed to through **{{ .Entry.Provider }}** provider has been {{ if (eq .Entry.Status "new") }}newly added{{ else }}updated{{ end }} on **{{ .Meta.Hostname }}**.
-
-This image has been {{ if (eq .Entry.Status "new") }}created{{ else }}updated{{ end }} at {{ .Entry.Manifest.Created.Format "Jan 02, 2006 15:04:05 UTC" }}
-with digest {{ .Entry.Manifest.Digest }} for {{ .Entry.Manifest.Platform }} platform.
-
-Need help, or have questions? Go to https://github.com/crazy-max/diun and leave an issue.
-
-`, tagTpl)))
- if err := emailTpl.Execute(&emailBuf, struct {
- Meta model.Meta
- Entry model.NotifEntry
- }{
+ message, err := msg.New(msg.Options{
Meta: c.meta,
Entry: entry,
- }); err != nil {
+ })
+ if err != nil {
return err
}
+
+ title, text, err := message.RenderMarkdownTemplate(customTpl)
+ if err != nil {
+ return err
+ }
+
email := hermes.Email{
Body: hermes.Body{
Title: fmt.Sprintf("%s 🔔 notification", c.meta.Name),
- FreeMarkdown: hermes.Markdown(emailBuf.String()),
- Signature: "Thanks for your support",
+ FreeMarkdown: hermes.Markdown(text),
+ Signature: "Thanks for your support!",
},
}
@@ -108,7 +98,7 @@ Need help, or have questions? Go to https://github.com/crazy-max/diun and leave
msg := gomail.NewMessage()
msg.SetHeader("From", fmt.Sprintf("%s <%s>", c.meta.Name, c.cfg.From))
msg.SetHeader("To", c.cfg.To)
- msg.SetHeader("Subject", subject)
+ msg.SetHeader("Subject", title)
msg.SetBody("text/plain", textpart)
msg.AddAlternative("text/html", htmlpart)
diff --git a/internal/notif/mail/theme.go b/internal/notif/mail/theme.go
index a29a2acd..b6628a18 100644
--- a/internal/notif/mail/theme.go
+++ b/internal/notif/mail/theme.go
@@ -174,7 +174,7 @@ func (t *Theme) HTMLTemplate() string {
}
cite {
display: block;
- font-size: 0.925rem;
+ font-size: 0.925rem;
}
cite:before {
content: "\2014 \0020";
@@ -304,92 +304,8 @@ func (t *Theme) HTMLTemplate() string {
{{ end }}
{{ if (ne .Email.Body.FreeMarkdown "") }}
{{ .Email.Body.FreeMarkdown.ToHTML }}
- {{ else }}
-
- {{ with .Email.Body.Dictionary }}
- {{ if gt (len .) 0 }}
-
-
|
- |||
{{ $action.Instructions }}
-| - - | -
{{ $line }}
@@ -398,13 +314,11 @@ func (t *Theme) HTMLTemplate() string { {{ end }}
- {{.Email.Body.Signature}},
-
- {{.Hermes.Product.Name}}
+ {{.Email.Body.Signature}}
{{ $action.Instructions }} {{ $action.Button.Link }}
+{{ $action.Instructions }} {{ $action.Button.Link }}
{{ end }} {{ end }} {{ end }} -{{ with .Email.Body.Outros }} +{{ with .Email.Body.Outros }} {{ range $line := . }}{{ $line }}
{{ end }} diff --git a/internal/notif/matrix/client.go b/internal/notif/matrix/client.go index 8e132ddd..b11c54e0 100644 --- a/internal/notif/matrix/client.go +++ b/internal/notif/matrix/client.go @@ -1,18 +1,15 @@ package matrix import ( - "bytes" "fmt" - "text/template" "github.com/crazy-max/diun/v4/internal/model" + "github.com/crazy-max/diun/v4/internal/msg" "github.com/crazy-max/diun/v4/internal/notif/notifier" "github.com/crazy-max/diun/v4/pkg/utl" "github.com/matrix-org/gomatrix" - "github.com/microcosm-cc/bluemonday" "github.com/pkg/errors" "github.com/rs/zerolog/log" - "github.com/russross/blackfriday/v2" ) // Client represents an active rocketchat notification object @@ -74,29 +71,26 @@ func (c *Client) Send(entry model.NotifEntry) error { return errors.Wrap(err, "failed to join room") } - tagTpl := "**{{ .Entry.Image.Domain }}/{{ .Entry.Image.Path }}:{{ .Entry.Image.Tag }}**" - if len(entry.Image.HubLink) > 0 { - tagTpl = "[**{{ .Entry.Image.Domain }}/{{ .Entry.Image.Path }}:{{ .Entry.Image.Tag }}**]({{ .Entry.Image.HubLink }})" - } - - var msgBuf bytes.Buffer - msgTpl := template.Must(template.New("text").Parse(fmt.Sprintf("Docker tag %s which you subscribed to through {{ .Entry.Provider }} provider has been {{ if (eq .Entry.Status \"new\") }}newly added{{ else }}updated{{ end }} on {{ .Meta.Hostname }}.", tagTpl))) - if err := msgTpl.Execute(&msgBuf, struct { - Meta model.Meta - Entry model.NotifEntry - }{ + message, err := msg.New(msg.Options{ Meta: c.meta, Entry: entry, - }); err != nil { + }) + if err != nil { return err } - msgHTML := bluemonday.UGCPolicy().SanitizeBytes( - blackfriday.Run(msgBuf.Bytes()), - ) + _, msgText, err := message.RenderMarkdown() + if err != nil { + return err + } + + _, msgHTML, err := message.RenderHTML() + if err != nil { + return err + } if _, err := m.SendMessageEvent(joined.RoomID, "m.room.message", gomatrix.HTMLMessage{ - Body: msgBuf.String(), + Body: string(msgText), MsgType: fmt.Sprintf("m.%s", c.cfg.MsgType), Format: "org.matrix.custom.html", FormattedBody: string(msgHTML), diff --git a/internal/notif/mqtt/client.go b/internal/notif/mqtt/client.go index 9f9d9766..7b16397d 100644 --- a/internal/notif/mqtt/client.go +++ b/internal/notif/mqtt/client.go @@ -1,11 +1,10 @@ package mqtt import ( - "encoding/json" "fmt" - "time" "github.com/crazy-max/diun/v4/internal/model" + "github.com/crazy-max/diun/v4/internal/msg" "github.com/crazy-max/diun/v4/internal/notif/notifier" "github.com/crazy-max/diun/v4/pkg/utl" MQTT "github.com/eclipse/paho.mqtt.golang" @@ -64,32 +63,20 @@ func (c *Client) Send(entry model.NotifEntry) error { } } - message, err := json.Marshal(struct { - Version string `json:"diun_version"` - Hostname string `json:"hostname"` - Status string `json:"status"` - Provider string `json:"provider"` - Image string `json:"image"` - HubLink string `json:"hub_link"` - MIMEType string `json:"mime_type"` - Created *time.Time `json:"created"` - Platform string `json:"platform"` - }{ - Version: c.meta.Version, - Hostname: c.meta.Hostname, - Status: string(entry.Status), - Provider: entry.Provider, - Image: entry.Image.String(), - HubLink: entry.Image.HubLink, - MIMEType: entry.Manifest.MIMEType, - Created: entry.Manifest.Created, - Platform: entry.Manifest.Platform, + message, err := msg.New(msg.Options{ + Meta: c.meta, + Entry: entry, }) if err != nil { return err } - token := c.mqttClient.Publish(c.cfg.Topic, byte(c.cfg.QoS), false, message) + body, err := message.RenderJSON() + if err != nil { + return err + } + + token := c.mqttClient.Publish(c.cfg.Topic, byte(c.cfg.QoS), false, body) token.Wait() return token.Error() } diff --git a/internal/notif/pushover/pushover.go b/internal/notif/pushover/client.go similarity index 61% rename from internal/notif/pushover/pushover.go rename to internal/notif/pushover/client.go index b3d245a8..909dc8c3 100644 --- a/internal/notif/pushover/pushover.go +++ b/internal/notif/pushover/client.go @@ -1,13 +1,11 @@ package pushover import ( - "bytes" "errors" - "fmt" - "text/template" "time" "github.com/crazy-max/diun/v4/internal/model" + "github.com/crazy-max/diun/v4/internal/msg" "github.com/crazy-max/diun/v4/internal/notif/notifier" "github.com/crazy-max/diun/v4/pkg/utl" "github.com/gregdel/pushover" @@ -50,25 +48,16 @@ func (c *Client) Send(entry model.NotifEntry) error { app := pushover.New(token) user := pushover.NewRecipient(recipient) - 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()) + message, err := msg.New(msg.Options{ + Meta: c.meta, + Entry: entry, + }) + if err != nil { + return err } - tagTpl := "{{ .Entry.Image.Domain }}/{{ .Entry.Image.Path }}:{{ .Entry.Image.Tag }}" - if len(entry.Image.HubLink) > 0 { - tagTpl = `{{ .Entry.Image.Domain }}/{{ .Entry.Image.Path }}:{{ .Entry.Image.Tag }}` - } - - var msgBuf bytes.Buffer - msgTpl := template.Must(template.New("email").Parse(fmt.Sprintf("Docker tag %s which you subscribed to through {{ .Entry.Provider }} provider has been {{ if (eq .Entry.Status \"new\") }}newly added{{ else }}updated{{ end }} on {{ .Hostname }}.", tagTpl))) - if err := msgTpl.Execute(&msgBuf, struct { - Hostname string - Entry model.NotifEntry - }{ - Hostname: c.meta.Hostname, - Entry: entry, - }); err != nil { + title, text, err := message.RenderHTML() + if err != nil { return err } @@ -78,7 +67,7 @@ func (c *Client) Send(entry model.NotifEntry) error { } _, err = app.SendMessage(&pushover.Message{ - Message: msgBuf.String(), + Message: string(text), Title: title, Priority: c.cfg.Priority, URL: c.meta.URL, diff --git a/internal/notif/rocketchat/client.go b/internal/notif/rocketchat/client.go index e0306312..21d4066c 100644 --- a/internal/notif/rocketchat/client.go +++ b/internal/notif/rocketchat/client.go @@ -8,10 +8,10 @@ import ( "net/url" "path" "strconv" - "text/template" "time" "github.com/crazy-max/diun/v4/internal/model" + "github.com/crazy-max/diun/v4/internal/msg" "github.com/crazy-max/diun/v4/internal/notif/notifier" ) @@ -22,6 +22,8 @@ type Client struct { meta model.Meta } +const customTpl = `Docker tag {{ .Entry.Image }} which you subscribed to through {{ .Entry.Provider }} provider has been {{ if (eq .Entry.Status "new") }}newly added{{ else }}updated{{ end }} on {{ .Meta.Hostname }}.` + // New creates a new rocketchat notification instance func New(config *model.NotifRocketChat, meta model.Meta) notifier.Notifier { return notifier.Notifier{ @@ -44,20 +46,16 @@ func (c *Client) Send(entry model.NotifEntry) error { Timeout: *c.cfg.Timeout, } - 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 {{ .Entry.Image.Domain }}/{{ .Entry.Image.Path }}:{{ .Entry.Image.Tag }} which you subscribed to through {{ .Entry.Provider }} provider has been {{ if (eq .Entry.Status "new") }}newly added{{ else }}updated{{ end }}.`)) - if err := textTpl.Execute(&textBuf, struct { - Meta model.Meta - Entry model.NotifEntry - }{ + message, err := msg.New(msg.Options{ Meta: c.meta, Entry: entry, - }); err != nil { + }) + if err != nil { + return err + } + + title, text, err := message.RenderMarkdownTemplate(customTpl) + if err != nil { return err } @@ -104,7 +102,7 @@ func (c *Client) Send(entry model.NotifEntry) error { Text: title, Attachments: []Attachment{ { - Text: textBuf.String(), + Text: string(text), Ts: json.Number(strconv.FormatInt(time.Now().Unix(), 10)), Fields: fields, }, diff --git a/internal/notif/script/client.go b/internal/notif/script/client.go index 3931dea6..bbdd1ccb 100644 --- a/internal/notif/script/client.go +++ b/internal/notif/script/client.go @@ -2,12 +2,12 @@ package script import ( "bytes" - "fmt" "os" "os/exec" "strings" "github.com/crazy-max/diun/v4/internal/model" + "github.com/crazy-max/diun/v4/internal/msg" "github.com/crazy-max/diun/v4/internal/notif/notifier" "github.com/pkg/errors" "github.com/rs/zerolog/log" @@ -50,19 +50,16 @@ func (c *Client) Send(entry model.NotifEntry) error { cmd.Dir = c.cfg.Dir } + message, err := msg.New(msg.Options{ + Meta: c.meta, + Entry: entry, + }) + if err != nil { + return err + } + // Set env vars - cmd.Env = append(os.Environ(), []string{ - fmt.Sprintf("DIUN_VERSION=%s", c.meta.Version), - fmt.Sprintf("DIUN_HOSTNAME=%s", c.meta.Hostname), - fmt.Sprintf("DIUN_ENTRY_STATUS=%s", string(entry.Status)), - fmt.Sprintf("DIUN_ENTRY_PROVIDER=%s", entry.Provider), - fmt.Sprintf("DIUN_ENTRY_IMAGE=%s", entry.Image.String()), - fmt.Sprintf("DIUN_ENTRY_HUBLINK=%s", entry.Image.HubLink), - fmt.Sprintf("DIUN_ENTRY_MIMETYPE=%s", entry.Manifest.MIMEType), - fmt.Sprintf("DIUN_ENTRY_DIGEST=%s", entry.Manifest.Digest), - fmt.Sprintf("DIUN_ENTRY_CREATED=%s", entry.Manifest.Created), - fmt.Sprintf("DIUN_ENTRY_PLATFORM=%s", entry.Manifest.Platform), - }...) + cmd.Env = append(os.Environ(), message.RenderEnv()...) // Run if err := cmd.Run(); err != nil { diff --git a/internal/notif/slack/slack.go b/internal/notif/slack/client.go similarity index 82% rename from internal/notif/slack/slack.go rename to internal/notif/slack/client.go index 28f26ef3..ac1e076c 100644 --- a/internal/notif/slack/slack.go +++ b/internal/notif/slack/client.go @@ -1,14 +1,13 @@ package slack import ( - "bytes" "encoding/json" "fmt" "strconv" - "text/template" "time" "github.com/crazy-max/diun/v4/internal/model" + "github.com/crazy-max/diun/v4/internal/msg" "github.com/crazy-max/diun/v4/internal/notif/notifier" "github.com/nlopes/slack" ) @@ -20,6 +19,8 @@ type Client struct { meta model.Meta } +const customTpl = " Docker tag `{{ .Entry.Image }}` {{ if (eq .Entry.Status \"new\") }}newly added{{ else }}updated{{ end }}." + // New creates a new slack notification instance func New(config *model.NotifSlack, meta model.Meta) notifier.Notifier { return notifier.Notifier{ @@ -37,15 +38,16 @@ func (c *Client) Name() string { // Send creates and sends a slack notification with an entry func (c *Client) Send(entry model.NotifEntry) error { - var textBuf bytes.Buffer - textTpl := template.Must(template.New("text").Parse(" Docker tag `{{ .Entry.Image.Domain }}/{{ .Entry.Image.Path }}:{{ .Entry.Image.Tag }}` {{ if (eq .Entry.Status \"new\") }}newly added{{ else }}updated{{ end }}.")) - if err := textTpl.Execute(&textBuf, struct { - Meta model.Meta - Entry model.NotifEntry - }{ + message, err := msg.New(msg.Options{ Meta: c.meta, Entry: entry, - }); err != nil { + }) + if err != nil { + return err + } + + _, text, err := message.RenderMarkdownTemplate(customTpl) + if err != nil { return err } @@ -97,7 +99,7 @@ func (c *Client) Send(entry model.NotifEntry) error { AuthorSubname: "github.com/crazy-max/diun", AuthorLink: c.meta.URL, AuthorIcon: c.meta.Logo, - Text: textBuf.String(), + Text: string(text), Footer: fmt.Sprintf("%s © %d %s %s", c.meta.Author, time.Now().Year(), c.meta.Name, c.meta.Version), Fields: fields, Ts: json.Number(strconv.FormatInt(time.Now().Unix(), 10)), diff --git a/internal/notif/teams/client.go b/internal/notif/teams/client.go index 4c8c7a71..379438dd 100644 --- a/internal/notif/teams/client.go +++ b/internal/notif/teams/client.go @@ -3,12 +3,11 @@ package teams import ( "bytes" "encoding/json" - "fmt" "net/http" - "text/template" "time" "github.com/crazy-max/diun/v4/internal/model" + "github.com/crazy-max/diun/v4/internal/msg" "github.com/crazy-max/diun/v4/internal/notif/notifier" ) @@ -19,6 +18,9 @@ type Client struct { meta model.Meta } +const customTpl = "Docker tag {{ if .Entry.Image.HubLink }}[`{{ .Entry.Image }}`]({{ .Entry.Image.HubLink }}){{ else }}`{{ .Entry.Image }}`{{ end }}" + + "{{ if (eq .Entry.Status \"new\") }}newly added{{ else }}updated{{ end }}." + // New creates a new webhook notification instance func New(config *model.NotifTeams, meta model.Meta) notifier.Notifier { return notifier.Notifier{ @@ -53,20 +55,16 @@ func (c *Client) Send(entry model.NotifEntry) error { Timeout: time.Duration(10) * time.Second, } - tagTpl := "`{{ .Entry.Image.Domain }}/{{ .Entry.Image.Path }}:{{ .Entry.Image.Tag }}`" - if len(entry.Image.HubLink) > 0 { - tagTpl = "[`{{ .Entry.Image.Domain }}/{{ .Entry.Image.Path }}:{{ .Entry.Image.Tag }}`]({{ .Entry.Image.HubLink }})" - } - - var textBuf bytes.Buffer - textTpl := template.Must(template.New("text").Parse(fmt.Sprintf("Docker tag %s {{ if (eq .Entry.Status \"new\") }}newly added{{ else }}updated{{ end }}.", tagTpl))) - if err := textTpl.Execute(&textBuf, struct { - Meta model.Meta - Entry model.NotifEntry - }{ + message, err := msg.New(msg.Options{ Meta: c.meta, Entry: entry, - }); err != nil { + }) + if err != nil { + return err + } + + _, text, err := message.RenderMarkdownTemplate(customTpl) + if err != nil { return err } @@ -75,7 +73,7 @@ func (c *Client) Send(entry model.NotifEntry) error { themeColor = "0076D7" } - var body, err = json.Marshal(struct { + body, err := json.Marshal(struct { Type string `json:"@type"` Context string `json:"@context"` ThemeColor string `json:"themeColor"` @@ -85,9 +83,9 @@ func (c *Client) Send(entry model.NotifEntry) error { Type: "MessageCard", Context: "http://schema.org/extensions", ThemeColor: themeColor, - Summary: textBuf.String(), + Summary: string(text), Sections: []Sections{{ - ActivityTitle: textBuf.String(), + ActivityTitle: string(text), ActivitySubtitle: "Provider: " + entry.Provider, Facts: []Fact{ {"Hostname", c.meta.Hostname}, diff --git a/internal/notif/telegram/telegram.go b/internal/notif/telegram/client.go similarity index 54% rename from internal/notif/telegram/telegram.go rename to internal/notif/telegram/client.go index f73ebab8..67fc10f6 100644 --- a/internal/notif/telegram/telegram.go +++ b/internal/notif/telegram/client.go @@ -1,12 +1,11 @@ package telegram import ( - "bytes" - "fmt" "strings" "text/template" "github.com/crazy-max/diun/v4/internal/model" + "github.com/crazy-max/diun/v4/internal/msg" "github.com/crazy-max/diun/v4/internal/notif/notifier" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api" ) @@ -18,6 +17,10 @@ type Client struct { meta model.Meta } +const customTpl = `Docker tag {{ if .Entry.Image.HubLink }}[{{ .Entry.Image }}]({{ .Entry.Image.HubLink }}){{ else }}{{ .Entry.Image }}{{ end }} +which you subscribed to through {{ .Entry.Provider }} provider has been {{ if (eq .Entry.Status "new") }}newly added{{ else }}updated{{ end }} +on {{ escapeMarkdown .Meta.Hostname }}.` + // New creates a new Telegram notification instance func New(config *model.NotifTelegram, meta model.Meta) notifier.Notifier { return notifier.Notifier{ @@ -40,20 +43,25 @@ func (c *Client) Send(entry model.NotifEntry) error { return err } - tagTpl := "{{ .Entry.Image.Domain }}/{{ .Entry.Image.Path }}:{{ .Entry.Image.Tag }}" - if len(entry.Image.HubLink) > 0 { - tagTpl = "[{{ .Entry.Image.Domain }}/{{ .Entry.Image.Path }}:{{ .Entry.Image.Tag }}]({{ .Entry.Image.HubLink }})" + message, err := msg.New(msg.Options{ + Meta: c.meta, + Entry: entry, + TplFuncs: template.FuncMap{ + "escapeMarkdown": func(text string) string { + text = strings.ReplaceAll(text, "_", "\\_") + text = strings.ReplaceAll(text, "*", "\\*") + text = strings.ReplaceAll(text, "[", "\\[") + text = strings.ReplaceAll(text, "`", "\\`") + return text + }, + }, + }) + if err != nil { + return err } - var msgBuf bytes.Buffer - msgTpl := template.Must(template.New("email").Parse(fmt.Sprintf("Docker tag %s which you subscribed to through {{ .Entry.Provider }} provider has been {{ if (eq .Entry.Status \"new\") }}newly added{{ else }}updated{{ end }} on {{ .Hostname }}.", tagTpl))) - if err := msgTpl.Execute(&msgBuf, struct { - Hostname string - Entry model.NotifEntry - }{ - Hostname: escapeMarkdown(c.meta.Hostname), - Entry: entry, - }); err != nil { + _, text, err := message.RenderMarkdownTemplate(strings.ReplaceAll(customTpl, "\n", " ")) + if err != nil { return err } @@ -62,7 +70,7 @@ func (c *Client) Send(entry model.NotifEntry) error { BaseChat: tgbotapi.BaseChat{ ChatID: chatID, }, - Text: msgBuf.String(), + Text: string(text), ParseMode: "markdown", DisableWebPagePreview: true, }) @@ -73,11 +81,3 @@ func (c *Client) Send(entry model.NotifEntry) error { return nil } - -func escapeMarkdown(txt string) string { - txt = strings.ReplaceAll(txt, "_", "\\_") - txt = strings.ReplaceAll(txt, "*", "\\*") - txt = strings.ReplaceAll(txt, "[", "\\[") - txt = strings.ReplaceAll(txt, "`", "\\`") - return txt -} diff --git a/internal/notif/webhook/client.go b/internal/notif/webhook/client.go index f8030ba3..e73d37c1 100644 --- a/internal/notif/webhook/client.go +++ b/internal/notif/webhook/client.go @@ -2,13 +2,11 @@ package webhook import ( "bytes" - "encoding/json" "net/http" - "time" "github.com/crazy-max/diun/v4/internal/model" + "github.com/crazy-max/diun/v4/internal/msg" "github.com/crazy-max/diun/v4/internal/notif/notifier" - "github.com/opencontainers/go-digest" ) // Client represents an active webhook notification object @@ -39,33 +37,19 @@ func (c *Client) Send(entry model.NotifEntry) error { Timeout: *c.cfg.Timeout, } - body, err := json.Marshal(struct { - Version string `json:"diun_version"` - Hostname string `json:"hostname"` - Status string `json:"status"` - Provider string `json:"provider"` - Image string `json:"image"` - HubLink string `json:"hub_link"` - MIMEType string `json:"mime_type"` - Digest digest.Digest `json:"digest"` - Created *time.Time `json:"created"` - Platform string `json:"platform"` - }{ - Version: c.meta.Version, - Hostname: c.meta.Hostname, - Status: string(entry.Status), - Provider: entry.Provider, - Image: entry.Image.String(), - HubLink: entry.Image.HubLink, - MIMEType: entry.Manifest.MIMEType, - Digest: entry.Manifest.Digest, - Created: entry.Manifest.Created, - Platform: entry.Manifest.Platform, + message, err := msg.New(msg.Options{ + Meta: c.meta, + Entry: entry, }) if err != nil { return err } + body, err := message.RenderJSON() + if err != nil { + return err + } + req, err := http.NewRequest(c.cfg.Method, c.cfg.Endpoint, bytes.NewBuffer(body)) if err != nil { return err