zen/core/zoptions/notify.go

288 lines
8.5 KiB
Go

package zoptions
import (
"fmt"
"io"
"os"
"time"
)
type NotifyParameters struct {
// AlwaysSend makes sure this message when set to true
// is always sent no matter the condition.
//
// It will cause `zen` to ignore every other
// parameters and just send the message.
AlwaysSend bool
// InitialBackoff is the starting backoff.
//
// If not specified, the default duration is time.Minute.
InitialBackoff time.Duration
// MaxBackoff is the maximum backoff. If unset, the default value
// is time.Hour * 24.
MaxBackoff time.Duration
// BackoffFormula is a string that represents the formula to calculate the backoff time
// when multiple message with the same key is fired off repeatedly.
//
// It uses CEL (Common Expression Language) syntax. See: https://github.com/google/cel-go
// for the language spec.
//
// Expression must return a duration type.
//
// If invalid cel expression, empty string, or wrong return value, default formula will be used.
//
// Available variables:
//
// - `repeat`: the number of times the notification has been sent. type: int.
// - `last`: the last time the notification was sent. type: timestamp.
// - `prev_backoff`: the backoff value from previous evaluation. 0 if first seen. Type: duration.
// - `initial_backoff`: the initial_backoff value. Type: duration
// - `max_backoff`: the maximum backoff value. Type: duration
//
// Available Non-Standard functions:
//
// // pow multiplies the base with the exponent
// pow(base: double, exp: double) -> double
//
// // mult_dur_double multiplies the duration with the double value
// mult_dur_double(dur: duration, mult: double) -> duration
//
//
// Example (and also default formula):
//
// mult_dur_double(initial_backoff, pow(1.5, double(repeat))) > max_backoff
// ? max_backoff
// : mult_dur_double(initial_backoff, pow(1.5, double(repeat)))
BackoffFormula string
// Attachments is a list of NamedReader (files or buffer) that will be sent as attachments.
//
// Attachments are guaranteed to be closed wether the notification is sent successfully or not
// to avoid resource leaks.
Attachments []NamedReader
}
type NotifyOption interface {
Apply(parameters *NotifyParameters)
}
// NotifyOptionFunc is a shortcut to create a NotifyOption from a function.
type NotifyOptionFunc func(parameters *NotifyParameters)
func (f NotifyOptionFunc) Apply(parameters *NotifyParameters) {
f(parameters)
}
type NotifiyOptionBuilder []NotifyOption
func (no NotifiyOptionBuilder) Apply(parameters *NotifyParameters) {
for _, opt := range no {
opt.Apply(parameters)
}
}
// AlwaysSend makes sure this message when set to true
// is always sent no matter the condition.
//
// It will cause `zen` to ignore every other
// parameters and just send the message.
func (no NotifiyOptionBuilder) AlwaysSend(alwaysSend bool) NotifiyOptionBuilder {
return append(no, NotifyOptionFunc(func(parameters *NotifyParameters) {
parameters.AlwaysSend = alwaysSend
}))
}
// InitialBackoff is the starting backoff.
//
// If not specified, the default duration is time.Minute.
func (no NotifiyOptionBuilder) InitialBackoff(backoff time.Duration) NotifiyOptionBuilder {
return append(no, NotifyOptionFunc(func(parameters *NotifyParameters) {
parameters.InitialBackoff = backoff
}))
}
// MaxBackoff is the maximum backoff. If unset, the default value
// is time.Hour * 24.
func (no NotifiyOptionBuilder) MaxBackoff(maxBackoff time.Duration) NotifiyOptionBuilder {
return append(no, NotifyOptionFunc(func(parameters *NotifyParameters) {
parameters.MaxBackoff = maxBackoff
}))
}
// Attachments is a list attachments (files or buffers) that will be sent with the notification.
//
// Attachments are guaranteed to be closed wether the notification is sent successfully or not
// to avoid resource leaks.
func (no NotifiyOptionBuilder) Attachments(attachments ...NamedReader) NotifiyOptionBuilder {
return append(no, NotifyOptionFunc(func(parameters *NotifyParameters) {
parameters.Attachments = append(parameters.Attachments, attachments...)
}))
}
// BackoffFormula is a string that represents the formula to calculate the backoff time
// when multiple message with the same key is fired off repeatedly.
//
// It uses CEL (Common Expression Language) syntax. See: https://github.com/google/cel-go
// for the language spec.
//
// Expression must return a duration type.
//
// If invalid cel expression, empty string, or wrong return value, default formula will be used.
//
// Available variables:
//
// - `repeat`: the number of times the notification has been sent. type: int.
// - `last`: the last time the notification was sent. type: timestamp.
// - `prev_backoff`: the backoff value from previous evaluation. 0 if this message is first seen. Type: duration.
// - `initial_backoff`: the initial_backoff value. Type: duration
// - `max_backoff`: the maximum backoff value. Type: duration
//
// Available Non-Standard functions:
//
// // pow multiplies the base with the exponent
// pow(base: double, exp: double) -> double
//
// // mult_dur_double multiplies the duration with the double value
// mult_dur_double(dur: duration, mult: double) -> duration
//
// Example (and also default formula):
//
// mult_dur_double(initial_backoff, pow(1.5, double(repeat))) > max_backoff
// ? max_backoff
// : mult_dur_double(initial_backoff, pow(1.5, double(repeat)))
func (no NotifiyOptionBuilder) BackoffFormula(formula string) NotifiyOptionBuilder {
return append(no, NotifyOptionFunc(func(parameters *NotifyParameters) {
parameters.BackoffFormula = formula
}))
}
func Notify() NotifiyOptionBuilder {
return NotifiyOptionBuilder{}
}
// NamedReader is an interface that extends io.ReadCloser with
// Filename and MimeType methods.
//
// This is used to pass a file with its metadata to the notification
// system.
type NamedReader interface {
io.ReadCloser
// Filename returns the name of the file.
//
// Filename will be sluggified and used as the filename
// in the notification.
Filename() string
// MimeType returns the mime type of the file.
//
// MimeType is used by Zen to determine the type of the file.
//
// Use constant values from: https://github.com/ldez/mimetype
// for maximum compatibility.
//
// Attachment like images, videos, etc, using well known mime types
// that are supported by browsers can
// be displayed by the browser.
//
// Mimetypes that are supported by browsers:
//
// - image/png
// - image/jpeg
// - image/gif
// - image/svg+xml
// - video/mp4
// - video/webm
// - video/ogg
// - audio/mpeg
// - audio/ogg
// - audio/wav
//
// Documents like pdf, docx, xlsx, will be given a download link and proper icons.
// How they are handled is up to the browser.
//
// If the mimetype is not supported, it will be given a download link with a generic icon.
MimeType() string
}
// NewNamedReader creates a new NamedReader from the given filename, mimeType and reader.
//
// If reader implements io.Closer, it will be closed when the NamedReader is closed.
//
// Otherwise, the reader will be wrapped with io.NopCloser.
func NewNamedReader(filename, mimeType string, reader io.Reader) NamedReader {
var rc io.ReadCloser
if c, ok := reader.(io.ReadCloser); ok {
rc = c
} else {
rc = io.NopCloser(reader)
}
return &namedReader{
filename: filename,
mimeType: mimeType,
reader: rc,
}
}
// NewNamedReaderFromFile same as NewNamedReader but reads the file from the filesystem.
//
// If the file does not exist or there is an error in permissions on reading the file,
// the error will be logged but otherwise ignored.
//
// Example:
//
// NewNamedReaderFromFile("path/to/file.txt", "text/plain")
func NewNamedReaderFromFile(path string, mimetype string) NamedReader {
var rc io.ReadCloser
f, err := os.Open(path)
if err != nil {
rc = &errorReader{err}
} else {
if stat, err := f.Stat(); err == nil && stat.IsDir() {
f.Close()
rc = &errorReader{fmt.Errorf("file %s is a directory", path)}
} else {
rc = f
}
}
return &namedReader{
filename: path,
mimeType: mimetype,
reader: rc,
}
}
type namedReader struct {
filename string
mimeType string
reader io.ReadCloser
}
func (na *namedReader) Read(p []byte) (n int, err error) {
return na.reader.Read(p)
}
func (na *namedReader) Close() error {
return na.reader.Close()
}
func (na *namedReader) Filename() string {
return na.filename
}
func (na *namedReader) MimeType() string {
return na.mimeType
}
type errorReader struct {
err error
}
func (er *errorReader) Read(p []byte) (n int, err error) {
return 0, er.err
}
func (er *errorReader) Close() error {
return nil
}