mirror of
https://github.com/containrrr/watchtower.git
synced 2025-12-16 23:20:12 +01:00
add slack notification cli flag
This commit is contained in:
parent
265ae80099
commit
b6707f504e
6 changed files with 159 additions and 19 deletions
92
actions/notify_slack.go
Normal file
92
actions/notify_slack.go
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mozillazg/request"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SlackNotifier is used to make the https requests to a specified slack
|
||||||
|
// webhook URL
|
||||||
|
type SlackNotifier struct {
|
||||||
|
slackURL string
|
||||||
|
identity string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
slackMessageStartup = "Watchtower startup"
|
||||||
|
slackMessageError = "Some errors while checking and redeployment (Please check logs):"
|
||||||
|
slackMessageSuccess = "Successfully redeployed images:"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewSlackNotifier instantiates a new SlackNotifier with an URL and an identifier which will
|
||||||
|
// be prepended to each message it sends
|
||||||
|
func NewSlackNotifier(slackURL, identity string) *SlackNotifier {
|
||||||
|
identity = strings.Trim(identity, " ")
|
||||||
|
|
||||||
|
if len(identity) != 0 {
|
||||||
|
identity = fmt.Sprintf("[%s]: ", identity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SlackNotifier{
|
||||||
|
slackURL: slackURL,
|
||||||
|
identity: identity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SlackNotifier) sendNotification(json map[string]interface{}) {
|
||||||
|
c := new(http.Client)
|
||||||
|
req := request.NewRequest(c)
|
||||||
|
req.Json = json
|
||||||
|
_, err := req.Post(s.slackURL)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyStartup sends a startup message to slack
|
||||||
|
func (s SlackNotifier) NotifyStartup() {
|
||||||
|
s.sendNotification(map[string]interface{}{
|
||||||
|
"text": fmt.Sprintf("%s%s", s.identity, slackMessageStartup),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildAttachment(items []string, title, color string) map[string]interface{} {
|
||||||
|
|
||||||
|
var fields []map[string]string
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
fields = append(fields, map[string]string{"value": item, "short": "false"})
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"fallback": title + strings.Join(items, ", "),
|
||||||
|
"color": color,
|
||||||
|
"title": title,
|
||||||
|
"fields": fields,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotifyContainerUpdate sends a Message after updating containers which yielded either success or errors or both
|
||||||
|
func (s SlackNotifier) NotifyContainerUpdate(successfulContainers, errorMessages []string) {
|
||||||
|
|
||||||
|
var attachments []map[string]interface{}
|
||||||
|
|
||||||
|
if len(successfulContainers) != 0 {
|
||||||
|
attachments = append(attachments, buildAttachment(successfulContainers, slackMessageSuccess, "good"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errorMessages) != 0 {
|
||||||
|
attachments = append(attachments, buildAttachment(errorMessages, slackMessageError, "danger"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// add a pretext to the first attachment
|
||||||
|
attachments[0]["pretext"] = s.identity
|
||||||
|
|
||||||
|
s.sendNotification(map[string]interface{}{
|
||||||
|
"attachments": attachments,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package actions
|
package actions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -34,12 +35,18 @@ func containerFilter(names []string) container.Filter {
|
||||||
// used to start those containers have been updated. If a change is detected in
|
// used to start those containers have been updated. If a change is detected in
|
||||||
// any of the images, the associated containers are stopped and restarted with
|
// any of the images, the associated containers are stopped and restarted with
|
||||||
// the new image.
|
// the new image.
|
||||||
func Update(client container.Client, names []string, cleanup bool, noRestart bool) error {
|
func Update(client container.Client, names []string, cleanup bool, noRestart bool) ([]string, []string, error) {
|
||||||
log.Info("Checking containers for updated images")
|
log.Info("Checking containers for updated images")
|
||||||
|
|
||||||
|
// helper vars for notification
|
||||||
|
var (
|
||||||
|
updatedContainers []string
|
||||||
|
errorMessages []string
|
||||||
|
)
|
||||||
|
|
||||||
containers, err := client.ListContainers(containerFilter(names))
|
containers, err := client.ListContainers(containerFilter(names))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return updatedContainers, errorMessages, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, container := range containers {
|
for i, container := range containers {
|
||||||
|
|
@ -48,13 +55,14 @@ func Update(client container.Client, names []string, cleanup bool, noRestart boo
|
||||||
log.Infof("Unable to update container %s. Proceeding to next.", containers[i].Name())
|
log.Infof("Unable to update container %s. Proceeding to next.", containers[i].Name())
|
||||||
log.Debug(err)
|
log.Debug(err)
|
||||||
stale = false
|
stale = false
|
||||||
|
errorMessages = append(errorMessages, fmt.Sprintf("Unable to update container %s: %v", container.Details(), err))
|
||||||
}
|
}
|
||||||
containers[i].Stale = stale
|
containers[i].Stale = stale
|
||||||
}
|
}
|
||||||
|
|
||||||
containers, err = container.SortByDependencies(containers)
|
containers, err = container.SortByDependencies(containers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return updatedContainers, errorMessages, err
|
||||||
}
|
}
|
||||||
|
|
||||||
checkDependencies(containers)
|
checkDependencies(containers)
|
||||||
|
|
@ -70,6 +78,7 @@ func Update(client container.Client, names []string, cleanup bool, noRestart boo
|
||||||
if container.Stale {
|
if container.Stale {
|
||||||
if err := client.StopContainer(container, waitTime); err != nil {
|
if err := client.StopContainer(container, waitTime); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
|
errorMessages = append(errorMessages, fmt.Sprintf("Unable to stop container %s: %v", container.Details(), err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -84,6 +93,7 @@ func Update(client container.Client, names []string, cleanup bool, noRestart boo
|
||||||
if container.IsWatchtower() {
|
if container.IsWatchtower() {
|
||||||
if err := client.RenameContainer(container, randName()); err != nil {
|
if err := client.RenameContainer(container, randName()); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
|
errorMessages = append(errorMessages, fmt.Sprintf("Unable to rename container %s: %v", container.Details(), err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,16 +101,19 @@ func Update(client container.Client, names []string, cleanup bool, noRestart boo
|
||||||
if !noRestart {
|
if !noRestart {
|
||||||
if err := client.StartContainer(container); err != nil {
|
if err := client.StartContainer(container); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
|
errorMessages = append(errorMessages, fmt.Sprintf("Unable to restart container %s: %v", container.Details(), err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cleanup {
|
if cleanup {
|
||||||
client.RemoveImage(container)
|
client.RemoveImage(container)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatedContainers = append(updatedContainers, container.Details())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return updatedContainers, errorMessages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkDependencies(containers []container.Container) {
|
func checkDependencies(containers []container.Container) {
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,12 @@ func (c Container) ImageName() string {
|
||||||
return imageName
|
return imageName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Details returns the name of the Docker image as defined in ImageName
|
||||||
|
// together with the name of the container
|
||||||
|
func (c Container) Details() string {
|
||||||
|
return fmt.Sprintf("%s (%s)", c.ImageName(), c.Name())
|
||||||
|
}
|
||||||
|
|
||||||
// Links returns a list containing the names of all the containers to which
|
// Links returns a list containing the names of all the containers to which
|
||||||
// this container is linked.
|
// this container is linked.
|
||||||
func (c Container) Links() []string {
|
func (c Container) Links() []string {
|
||||||
|
|
|
||||||
23
glide.lock
generated
23
glide.lock
generated
|
|
@ -1,10 +1,12 @@
|
||||||
hash: 9ddd729b207d71ce16ae9a0282ac87a046b9161ac4f15b108e86a03367172516
|
hash: 79bf30c3a98ecbe4c7668765f0033f5e487836d2011b21bcd219d321ace35f13
|
||||||
updated: 2017-01-24T20:45:43.1914053+01:00
|
updated: 2017-02-07T15:03:18.178257775+01:00
|
||||||
imports:
|
imports:
|
||||||
- name: github.com/Azure/go-ansiterm
|
- name: github.com/Azure/go-ansiterm
|
||||||
version: 388960b655244e76e24c75f48631564eaefade62
|
version: 388960b655244e76e24c75f48631564eaefade62
|
||||||
subpackages:
|
subpackages:
|
||||||
- winterm
|
- winterm
|
||||||
|
- name: github.com/bitly/go-simplejson
|
||||||
|
version: aabad6e819789e569bd6aabf444c935aa9ba1e44
|
||||||
- name: github.com/davecgh/go-spew
|
- name: github.com/davecgh/go-spew
|
||||||
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
|
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
|
||||||
subpackages:
|
subpackages:
|
||||||
|
|
@ -101,7 +103,7 @@ imports:
|
||||||
- name: github.com/docker/libtrust
|
- name: github.com/docker/libtrust
|
||||||
version: 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
version: 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
||||||
- name: github.com/golang/protobuf
|
- name: github.com/golang/protobuf
|
||||||
version: 1f49d83d9aa00e6ce4fc8258c71cc7786aec968a
|
version: f7137ae6b19afbfd61a94b746fda3b3fe0491874
|
||||||
subpackages:
|
subpackages:
|
||||||
- proto
|
- proto
|
||||||
- name: github.com/gorilla/context
|
- name: github.com/gorilla/context
|
||||||
|
|
@ -114,16 +116,17 @@ imports:
|
||||||
version: f4e566c536cf69158e808ec28ef4182a37fdc981
|
version: f4e566c536cf69158e808ec28ef4182a37fdc981
|
||||||
- name: github.com/Microsoft/go-winio
|
- name: github.com/Microsoft/go-winio
|
||||||
version: 24a3e3d3fc7451805e09d11e11e95d9a0a4f205e
|
version: 24a3e3d3fc7451805e09d11e11e95d9a0a4f205e
|
||||||
|
- name: github.com/mozillazg/request
|
||||||
|
version: 052232e32456da4751d789b61e89aba4a07a7e25
|
||||||
- name: github.com/opencontainers/runc
|
- name: github.com/opencontainers/runc
|
||||||
version: 2f7393a47307a16f8cee44a37b262e8b81021e3e
|
version: b263a43430ac6996a4302b891688544225197294
|
||||||
repo: https://github.com/docker/runc.git
|
|
||||||
subpackages:
|
subpackages:
|
||||||
- libcontainer/configs
|
- libcontainer/configs
|
||||||
- libcontainer/devices
|
- libcontainer/devices
|
||||||
- libcontainer/system
|
- libcontainer/system
|
||||||
- libcontainer/user
|
- libcontainer/user
|
||||||
- name: github.com/opencontainers/runtime-spec
|
- name: github.com/opencontainers/runtime-spec
|
||||||
version: 1c7c27d043c2a5e513a44084d2b10d77d1402b8c
|
version: 794ca7ac88234607f9d2c76da8a6e9bbbade8cb9
|
||||||
subpackages:
|
subpackages:
|
||||||
- specs-go
|
- specs-go
|
||||||
- name: github.com/pkg/errors
|
- name: github.com/pkg/errors
|
||||||
|
|
@ -135,10 +138,9 @@ imports:
|
||||||
- name: github.com/robfig/cron
|
- name: github.com/robfig/cron
|
||||||
version: 9585fd555638e77bba25f25db5c44b41f264aeb7
|
version: 9585fd555638e77bba25f25db5c44b41f264aeb7
|
||||||
- name: github.com/Sirupsen/logrus
|
- name: github.com/Sirupsen/logrus
|
||||||
version: d26492970760ca5d33129d2d799e34be5c4782eb
|
version: c078b1e43f58d563c74cebe63c85789e76ddb627
|
||||||
- name: github.com/spf13/cobra
|
- name: github.com/spf13/cobra
|
||||||
version: a3c09249f1a24a9d951f2738fb9b9256b8b42fa5
|
version: 35136c09d8da66b901337c6e86fd8e88a1a255bd
|
||||||
repo: https://github.com/dnephin/cobra.git
|
|
||||||
- name: github.com/spf13/pflag
|
- name: github.com/spf13/pflag
|
||||||
version: dabebe21bf790f782ea4c7bbd2efc430de182afd
|
version: dabebe21bf790f782ea4c7bbd2efc430de182afd
|
||||||
- name: github.com/stretchr/objx
|
- name: github.com/stretchr/objx
|
||||||
|
|
@ -157,7 +159,7 @@ imports:
|
||||||
- tar/asm
|
- tar/asm
|
||||||
- tar/storage
|
- tar/storage
|
||||||
- name: golang.org/x/net
|
- name: golang.org/x/net
|
||||||
version: 2beffdc2e92c8a3027590f898fe88f69af48a3f8
|
version: ""
|
||||||
repo: https://github.com/tonistiigi/net.git
|
repo: https://github.com/tonistiigi/net.git
|
||||||
subpackages:
|
subpackages:
|
||||||
- context
|
- context
|
||||||
|
|
@ -166,6 +168,7 @@ imports:
|
||||||
- http2/hpack
|
- http2/hpack
|
||||||
- internal/timeseries
|
- internal/timeseries
|
||||||
- proxy
|
- proxy
|
||||||
|
- publicsuffix
|
||||||
- trace
|
- trace
|
||||||
- name: golang.org/x/sys
|
- name: golang.org/x/sys
|
||||||
version: 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9
|
version: 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9
|
||||||
|
|
|
||||||
|
|
@ -26,3 +26,5 @@ import:
|
||||||
- context
|
- context
|
||||||
- package: github.com/robfig/cron
|
- package: github.com/robfig/cron
|
||||||
version: 9585fd555638e77bba25f25db5c44b41f264aeb7
|
version: 9585fd555638e77bba25f25db5c44b41f264aeb7
|
||||||
|
- package: github.com/mozillazg/request
|
||||||
|
version: v0.8.0
|
||||||
|
|
|
||||||
26
main.go
26
main.go
|
|
@ -27,6 +27,7 @@ var (
|
||||||
scheduleSpec string
|
scheduleSpec string
|
||||||
cleanup bool
|
cleanup bool
|
||||||
noRestart bool
|
noRestart bool
|
||||||
|
slackNotifier *actions.SlackNotifier
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -82,6 +83,15 @@ func main() {
|
||||||
Name: "debug",
|
Name: "debug",
|
||||||
Usage: "enable debug mode with verbose logging",
|
Usage: "enable debug mode with verbose logging",
|
||||||
},
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "slack-hook-url",
|
||||||
|
Usage: "the url for sending slack webhooks to",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "slack-message-identification",
|
||||||
|
Usage: "specify a string which will be used to identify the messages comming from this watchtower instance. Default if omitted is \"watchtower\"",
|
||||||
|
Value: "watchtower",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.Run(os.Args); err != nil {
|
if err := app.Run(os.Args); err != nil {
|
||||||
|
|
@ -108,6 +118,9 @@ func before(c *cli.Context) error {
|
||||||
cleanup = c.GlobalBool("cleanup")
|
cleanup = c.GlobalBool("cleanup")
|
||||||
noRestart = c.GlobalBool("no-restart")
|
noRestart = c.GlobalBool("no-restart")
|
||||||
|
|
||||||
|
slackURL := c.GlobalString("slack-hook-url")
|
||||||
|
slackPrefix := c.GlobalString("slack-message-identification")
|
||||||
|
|
||||||
// configure environment vars for client
|
// configure environment vars for client
|
||||||
err := envConfig(c)
|
err := envConfig(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -115,6 +128,12 @@ func before(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
client = container.NewClient(!c.GlobalBool("no-pull"))
|
client = container.NewClient(!c.GlobalBool("no-pull"))
|
||||||
|
|
||||||
|
if len(slackURL) != 0 {
|
||||||
|
slackNotifier = actions.NewSlackNotifier(slackURL, slackPrefix)
|
||||||
|
slackNotifier.NotifyStartup()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,9 +154,14 @@ func start(c *cli.Context) error {
|
||||||
select {
|
select {
|
||||||
case v := <-tryLockSem:
|
case v := <-tryLockSem:
|
||||||
defer func() { tryLockSem <- v }()
|
defer func() { tryLockSem <- v }()
|
||||||
if err := actions.Update(client, names, cleanup, noRestart); err != nil {
|
updatedContainers, errorMessages, err := actions.Update(client, names, cleanup, noRestart)
|
||||||
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
if slackNotifier != nil && (len(updatedContainers) != 0 || len(errorMessages) != 0) {
|
||||||
|
slackNotifier.NotifyContainerUpdate(updatedContainers, errorMessages)
|
||||||
|
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
log.Debug("Skipped another update already running.")
|
log.Debug("Skipped another update already running.")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue