mirror of
https://github.com/crazy-max/diun.git
synced 2025-12-24 14:31:47 +01:00
Add script notification (#75)
* Add script notification (#53) * Fix SysProcAttr * Fix build constraint Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -25,7 +25,7 @@ about: Create a report to help us improve
|
||||
* Platform (windows/linux) :
|
||||
* System info (type `uname -a`) :
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
# paste your YAML configuration file here and remove sensitive data
|
||||
```
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
## Overview
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
db:
|
||||
path: diun.db
|
||||
|
||||
@@ -20,19 +20,19 @@ watch:
|
||||
first_check_notif: false
|
||||
|
||||
notif:
|
||||
amqp:
|
||||
amqp:
|
||||
host: localhost
|
||||
port: 5672
|
||||
username: guest
|
||||
password: guest
|
||||
exchange:
|
||||
queue: queue
|
||||
gotify:
|
||||
gotify:
|
||||
endpoint: http://gotify.foo.com
|
||||
token: Token123456
|
||||
priority: 1
|
||||
timeout: 10
|
||||
mail:
|
||||
mail:
|
||||
host: localhost
|
||||
port: 25
|
||||
ssl: false
|
||||
@@ -41,15 +41,20 @@ notif:
|
||||
password:
|
||||
from:
|
||||
to:
|
||||
rocketchat:
|
||||
rocketchat:
|
||||
endpoint: http://rocket.foo.com:3000
|
||||
channel: "#general"
|
||||
user_id: abcdEFGH012345678
|
||||
token: Token123456
|
||||
timeout: 10
|
||||
slack:
|
||||
script:
|
||||
cmd: "myprogram"
|
||||
args:
|
||||
- "--anarg"
|
||||
- "another"
|
||||
slack:
|
||||
webhook_url: https://hooks.slack.com/services/ABCD12EFG/HIJK34LMN/01234567890abcdefghij
|
||||
teams:
|
||||
teams:
|
||||
webhook_url: https://outlook.office.com/webhook/ABCD12EFG/HIJK34LMN/01234567890abcdefghij
|
||||
telegram:
|
||||
token: aabbccdd:11223344
|
||||
@@ -101,6 +106,7 @@ providers:
|
||||
* [gotify](notifications.md#gotify)
|
||||
* [mail](notifications.md#mail)
|
||||
* [rocketchat](notifications.md#rocketchat)
|
||||
* [script](notifications.md#script)
|
||||
* [slack](notifications.md#slack)
|
||||
* [teams](notifications.md#teams)
|
||||
* [telegram](notifications.md#telegram)
|
||||
|
||||
@@ -8,7 +8,7 @@ If you encounter this kind of error, you are probably using the [file provider](
|
||||
|
||||
In the example below, Diun is running (`diun_x.x.x_windows_i386.zip`) on Windows 10 and tries to analyze the `crazymax/cloudflared` image with the detected platform (`windows/386)`:
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
- name: crazymax/cloudflared:2020.2.1
|
||||
watch_repo: true
|
||||
```
|
||||
@@ -22,7 +22,7 @@ Fri, 27 Mar 2020 01:20:03 UTC ERR Cannot list tags from registry error="Error ch
|
||||
|
||||
You have to force the platform for this image if you are not on a supported platform. For example:
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
- name: crazymax/cloudflared:2020.2.1
|
||||
watch_repo: true
|
||||
platform:
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* [Gotify](#gotify)
|
||||
* [Mail](#mail)
|
||||
* [Rocket.Chat](#rocketchat)
|
||||
* [Script](#script)
|
||||
* [Slack](#slack)
|
||||
* [Teams](#teams)
|
||||
* [Telegram](#telegram)
|
||||
@@ -83,6 +84,28 @@ To be able to send notifications to your Rocket.Chat channel:
|
||||
|
||||

|
||||
|
||||
## Script
|
||||
|
||||
You can send script notifications with the following settings:
|
||||
|
||||
* `script`
|
||||
* `cmd`: Command or script to execute. **required**
|
||||
* `args`: List of args to pass to `cmd`.
|
||||
* `dir`: Specifies the working directory of the command.
|
||||
|
||||
Following environment variables are passed to the process and will look like this:
|
||||
|
||||
```
|
||||
DIUN_VERSION=3.0.0
|
||||
DIUN_ENTRY_STATUS=new
|
||||
DIUN_ENTRY_PROVIDER=file
|
||||
DIUN_ENTRY_IMAGE=docker.io/crazymax/diun:latest
|
||||
DIUN_ENTRY_MIMETYPE=application/vnd.docker.distribution.manifest.list.v2+json
|
||||
DIUN_ENTRY_DIGEST=sha256:216e3ae7de4ca8b553eb11ef7abda00651e79e537e85c46108284e5e91673e01
|
||||
DIUN_ENTRY_CREATED=2020-03-26 12:23:56 +0000 UTC
|
||||
DIUN_ENTRY_PLATFORM=linux/adm64
|
||||
```
|
||||
|
||||
## Slack
|
||||
|
||||
You can send notifications to your Slack channel using an [incoming webhook URL](https://api.slack.com/messaging/webhooks):
|
||||
|
||||
@@ -15,7 +15,7 @@ In this section we quickly go over a basic docker-compose file using your local
|
||||
|
||||
First of all, let's create a Diun configuration we named `diun.yml`:
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
watch:
|
||||
workers: 20
|
||||
schedule: "*/30 * * * *"
|
||||
@@ -29,7 +29,7 @@ Here we use a single Docker provider with a minimum configuration to analyze lab
|
||||
|
||||
Now let's create a simple docker-compose file with Diun and some simple services:
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
version: "3.5"
|
||||
|
||||
services:
|
||||
|
||||
@@ -16,7 +16,7 @@ The file provider lets you define Docker images to analyze through a YAML file o
|
||||
|
||||
Register the file provider:
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
db:
|
||||
path: diun.db
|
||||
|
||||
@@ -39,7 +39,7 @@ providers:
|
||||
filename: /path/to/config.yml
|
||||
```
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
### /path/to/config.yml
|
||||
|
||||
# Watch latest tag of crazymax/nextcloud image on docker.io (DockerHub) with registry ID 'someregistryoptions'.
|
||||
@@ -82,7 +82,7 @@ providers:
|
||||
|
||||
Let's take a look with a simple example:
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
db:
|
||||
path: diun.db
|
||||
|
||||
@@ -100,7 +100,7 @@ providers:
|
||||
filename: /path/to/config.yml
|
||||
```
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
# /path/to/config.yml
|
||||
- name: crazymax/cloudflared
|
||||
watch_repo: true
|
||||
@@ -137,7 +137,7 @@ Defines the path to the [configuration file](#yaml-configuration-file).
|
||||
|
||||
> :warning: `filename` and `directory` are mutually exclusive.
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
providers:
|
||||
file:
|
||||
filename: /path/to/config/conf.yml
|
||||
@@ -149,7 +149,7 @@ Defines the path to the directory that contains the [configuration files](#yaml-
|
||||
|
||||
> :warning: `filename` and `directory` are mutually exclusive.
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
providers:
|
||||
file:
|
||||
directory: /path/to/config
|
||||
|
||||
@@ -15,7 +15,7 @@ In this section we quickly go over a basic stack using your local swarm cluster.
|
||||
|
||||
First of all, let's create a Diun configuration we named `diun.yml`:
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
watch:
|
||||
workers: 20
|
||||
schedule: "*/30 * * * *"
|
||||
@@ -28,7 +28,7 @@ Here we use our local Swarm provider with a minimum configuration to analyze lab
|
||||
|
||||
Now let's create a simple stack for Diun:
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
version: "3.5"
|
||||
|
||||
services:
|
||||
@@ -50,7 +50,7 @@ services:
|
||||
|
||||
And another one with a simple service:
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
version: "3.5"
|
||||
|
||||
services:
|
||||
|
||||
@@ -24,18 +24,18 @@ func TestLoad(t *testing.T) {
|
||||
{
|
||||
name: "Fail on wrong file format",
|
||||
cli: model.Cli{
|
||||
Cfgfile: "config.invalid.yml",
|
||||
Cfgfile: "./test/config.invalid.yml",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Success",
|
||||
cli: model.Cli{
|
||||
Cfgfile: "config.test.yml",
|
||||
Cfgfile: "./test/config.test.yml",
|
||||
},
|
||||
wantData: &config.Config{
|
||||
Cli: model.Cli{
|
||||
Cfgfile: "config.test.yml",
|
||||
Cfgfile: "./test/config.test.yml",
|
||||
},
|
||||
App: model.App{
|
||||
ID: "diun",
|
||||
@@ -82,6 +82,12 @@ func TestLoad(t *testing.T) {
|
||||
Token: "Token123456",
|
||||
Timeout: 10,
|
||||
},
|
||||
Script: &model.NotifScript{
|
||||
Cmd: "go",
|
||||
Args: []string{
|
||||
"version",
|
||||
},
|
||||
},
|
||||
Slack: &model.NotifSlack{
|
||||
WebhookURL: "https://hooks.slack.com/services/ABCD12EFG/HIJK34LMN/01234567890abcdefghij",
|
||||
},
|
||||
@@ -126,7 +132,7 @@ func TestLoad(t *testing.T) {
|
||||
WatchByDefault: utl.NewTrue(),
|
||||
},
|
||||
File: &model.PrdFile{
|
||||
Filename: "./dummy.yml",
|
||||
Filename: "./test/dummy.yml",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"net/mail"
|
||||
"os/exec"
|
||||
|
||||
"github.com/crazy-max/diun/internal/model"
|
||||
"github.com/crazy-max/diun/pkg/utl"
|
||||
@@ -26,6 +27,9 @@ func (cfg *Config) validateNotif() error {
|
||||
if err := cfg.validateNotifRocketChat(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cfg.validateNotifScript(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cfg.validateNotifSlack(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -106,6 +110,22 @@ func (cfg *Config) validateNotifRocketChat() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *Config) validateNotifScript() error {
|
||||
if cfg.Notif.Script == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cfg.Notif.Script.Cmd == "" {
|
||||
return errors.New("command required for script provider")
|
||||
}
|
||||
|
||||
if _, err := exec.LookPath(cfg.Notif.Script.Cmd); err != nil {
|
||||
return errors.Wrap(err, "command not found for script provider")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *Config) validateNotifSlack() error {
|
||||
if cfg.Notif.Slack == nil {
|
||||
return nil
|
||||
|
||||
@@ -35,9 +35,13 @@ notif:
|
||||
user_id: abcdEFGH012345678
|
||||
token: Token123456
|
||||
timeout: 10
|
||||
script:
|
||||
cmd: "go"
|
||||
args:
|
||||
- "version"
|
||||
slack:
|
||||
webhook_url: https://hooks.slack.com/services/ABCD12EFG/HIJK34LMN/01234567890abcdefghij
|
||||
teams:
|
||||
teams:
|
||||
webhook_url: https://outlook.office.com/webhook/ABCD12EFG/HIJK34LMN/01234567890abcdefghij
|
||||
telegram:
|
||||
token: abcdef123456
|
||||
@@ -69,4 +73,4 @@ providers:
|
||||
swarm:
|
||||
watch_by_default: true
|
||||
file:
|
||||
filename: ./dummy.yml
|
||||
filename: ./test/dummy.yml
|
||||
1
internal/config/test/dummy.yml
Normal file
1
internal/config/test/dummy.yml
Normal file
@@ -0,0 +1 @@
|
||||
# noop
|
||||
1
internal/config/test/myscript.sh
Normal file
1
internal/config/test/myscript.sh
Normal file
@@ -0,0 +1 @@
|
||||
# noop
|
||||
@@ -18,6 +18,7 @@ type Notif struct {
|
||||
Gotify *NotifGotify `yaml:"gotify,omitempty"`
|
||||
Mail *NotifMail `yaml:"mail,omitempty"`
|
||||
RocketChat *NotifRocketChat `yaml:"rocketchat,omitempty"`
|
||||
Script *NotifScript `yaml:"script,omitempty"`
|
||||
Slack *NotifSlack `yaml:"slack,omitempty"`
|
||||
Teams *NotifTeams `yaml:"teams,omitempty"`
|
||||
Telegram *NotifTelegram `yaml:"telegram,omitempty"`
|
||||
@@ -67,6 +68,13 @@ type NotifRocketChat struct {
|
||||
Timeout int `yaml:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
// NotifScript holds script notification configuration details
|
||||
type NotifScript struct {
|
||||
Cmd string `yaml:"cmd,omitempty"`
|
||||
Args []string `yaml:"args,omitempty"`
|
||||
Dir string `yaml:"dir,omitempty"`
|
||||
}
|
||||
|
||||
// NotifSlack holds slack notification configuration details
|
||||
type NotifSlack struct {
|
||||
WebhookURL string `yaml:"webhook_url,omitempty"`
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"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/script"
|
||||
"github.com/crazy-max/diun/internal/notif/slack"
|
||||
"github.com/crazy-max/diun/internal/notif/teams"
|
||||
"github.com/crazy-max/diun/internal/notif/telegram"
|
||||
@@ -47,6 +48,9 @@ func New(config *model.Notif, app model.App, userAgent string) (*Client, error)
|
||||
if config.RocketChat != nil {
|
||||
c.notifiers = append(c.notifiers, rocketchat.New(config.RocketChat, app, userAgent))
|
||||
}
|
||||
if config.Script != nil {
|
||||
c.notifiers = append(c.notifiers, script.New(config.Script, app))
|
||||
}
|
||||
if config.Slack != nil {
|
||||
c.notifiers = append(c.notifiers, slack.New(config.Slack, app))
|
||||
}
|
||||
|
||||
73
internal/notif/script/client.go
Normal file
73
internal/notif/script/client.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package script
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/crazy-max/diun/internal/model"
|
||||
"github.com/crazy-max/diun/internal/notif/notifier"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// Client represents an active script notification object
|
||||
type Client struct {
|
||||
*notifier.Notifier
|
||||
cfg *model.NotifScript
|
||||
app model.App
|
||||
userAgent string
|
||||
}
|
||||
|
||||
// New creates a new script notification instance
|
||||
func New(config *model.NotifScript, 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 "script"
|
||||
}
|
||||
|
||||
// Send creates and sends a script notification with an entry
|
||||
func (c *Client) Send(entry model.NotifEntry) error {
|
||||
cmd := exec.Command(c.cfg.Cmd, c.cfg.Args...)
|
||||
setSysProcAttr(cmd)
|
||||
|
||||
// Capture output
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
// Set working dir
|
||||
if c.cfg.Dir != "" {
|
||||
cmd.Dir = c.cfg.Dir
|
||||
}
|
||||
|
||||
// Set env vars
|
||||
cmd.Env = append(os.Environ(), []string{
|
||||
fmt.Sprintf("DIUN_VERSION=%s", c.app.Version),
|
||||
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_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),
|
||||
}...)
|
||||
|
||||
// Run
|
||||
if err := cmd.Run(); err != nil {
|
||||
return errors.Wrap(err, strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
|
||||
log.Debug().Msgf(strings.TrimSpace(stdout.String()))
|
||||
return nil
|
||||
}
|
||||
10
internal/notif/script/cmd.go
Normal file
10
internal/notif/script/cmd.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// +build !windows
|
||||
|
||||
package script
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func setSysProcAttr(cmd *exec.Cmd) {
|
||||
}
|
||||
10
internal/notif/script/cmd_windows.go
Normal file
10
internal/notif/script/cmd_windows.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package script
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func setSysProcAttr(cmd *exec.Cmd) {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||
}
|
||||
Reference in New Issue
Block a user