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 }