mirror of
https://github.com/containrrr/watchtower.git
synced 2025-12-16 15:10:12 +01:00
Merge b6707f504e into fec369fd01
This commit is contained in:
commit
798e1b2479
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"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
|
||||
// any of the images, the associated containers are stopped and restarted with
|
||||
// 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")
|
||||
|
||||
// helper vars for notification
|
||||
var (
|
||||
updatedContainers []string
|
||||
errorMessages []string
|
||||
)
|
||||
|
||||
containers, err := client.ListContainers(containerFilter(names))
|
||||
if err != nil {
|
||||
return err
|
||||
return updatedContainers, errorMessages, err
|
||||
}
|
||||
|
||||
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.Debug(err)
|
||||
stale = false
|
||||
errorMessages = append(errorMessages, fmt.Sprintf("Unable to update container %s: %v", container.Details(), err))
|
||||
}
|
||||
containers[i].Stale = stale
|
||||
}
|
||||
|
||||
containers, err = container.SortByDependencies(containers)
|
||||
if err != nil {
|
||||
return err
|
||||
return updatedContainers, errorMessages, err
|
||||
}
|
||||
|
||||
checkDependencies(containers)
|
||||
|
|
@ -70,6 +78,7 @@ func Update(client container.Client, names []string, cleanup bool, noRestart boo
|
|||
if container.Stale {
|
||||
if err := client.StopContainer(container, waitTime); err != nil {
|
||||
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 err := client.RenameContainer(container, randName()); err != nil {
|
||||
log.Error(err)
|
||||
errorMessages = append(errorMessages, fmt.Sprintf("Unable to rename container %s: %v", container.Details(), err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
|
@ -91,16 +101,19 @@ func Update(client container.Client, names []string, cleanup bool, noRestart boo
|
|||
if !noRestart {
|
||||
if err := client.StartContainer(container); err != nil {
|
||||
log.Error(err)
|
||||
errorMessages = append(errorMessages, fmt.Sprintf("Unable to restart container %s: %v", container.Details(), err))
|
||||
}
|
||||
}
|
||||
|
||||
if cleanup {
|
||||
client.RemoveImage(container)
|
||||
}
|
||||
|
||||
updatedContainers = append(updatedContainers, container.Details())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return updatedContainers, errorMessages, nil
|
||||
}
|
||||
|
||||
func checkDependencies(containers []container.Container) {
|
||||
|
|
|
|||
|
|
@ -64,6 +64,12 @@ func (c Container) ImageName() string {
|
|||
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
|
||||
// this container is linked.
|
||||
func (c Container) Links() []string {
|
||||
|
|
|
|||
23
glide.lock
generated
23
glide.lock
generated
|
|
@ -1,10 +1,12 @@
|
|||
hash: 9ddd729b207d71ce16ae9a0282ac87a046b9161ac4f15b108e86a03367172516
|
||||
updated: 2017-01-24T20:45:43.1914053+01:00
|
||||
hash: 79bf30c3a98ecbe4c7668765f0033f5e487836d2011b21bcd219d321ace35f13
|
||||
updated: 2017-02-07T15:03:18.178257775+01:00
|
||||
imports:
|
||||
- name: github.com/Azure/go-ansiterm
|
||||
version: 388960b655244e76e24c75f48631564eaefade62
|
||||
subpackages:
|
||||
- winterm
|
||||
- name: github.com/bitly/go-simplejson
|
||||
version: aabad6e819789e569bd6aabf444c935aa9ba1e44
|
||||
- name: github.com/davecgh/go-spew
|
||||
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
|
||||
subpackages:
|
||||
|
|
@ -101,7 +103,7 @@ imports:
|
|||
- name: github.com/docker/libtrust
|
||||
version: 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
||||
- name: github.com/golang/protobuf
|
||||
version: 1f49d83d9aa00e6ce4fc8258c71cc7786aec968a
|
||||
version: f7137ae6b19afbfd61a94b746fda3b3fe0491874
|
||||
subpackages:
|
||||
- proto
|
||||
- name: github.com/gorilla/context
|
||||
|
|
@ -114,16 +116,17 @@ imports:
|
|||
version: f4e566c536cf69158e808ec28ef4182a37fdc981
|
||||
- name: github.com/Microsoft/go-winio
|
||||
version: 24a3e3d3fc7451805e09d11e11e95d9a0a4f205e
|
||||
- name: github.com/mozillazg/request
|
||||
version: 052232e32456da4751d789b61e89aba4a07a7e25
|
||||
- name: github.com/opencontainers/runc
|
||||
version: 2f7393a47307a16f8cee44a37b262e8b81021e3e
|
||||
repo: https://github.com/docker/runc.git
|
||||
version: b263a43430ac6996a4302b891688544225197294
|
||||
subpackages:
|
||||
- libcontainer/configs
|
||||
- libcontainer/devices
|
||||
- libcontainer/system
|
||||
- libcontainer/user
|
||||
- name: github.com/opencontainers/runtime-spec
|
||||
version: 1c7c27d043c2a5e513a44084d2b10d77d1402b8c
|
||||
version: 794ca7ac88234607f9d2c76da8a6e9bbbade8cb9
|
||||
subpackages:
|
||||
- specs-go
|
||||
- name: github.com/pkg/errors
|
||||
|
|
@ -135,10 +138,9 @@ imports:
|
|||
- name: github.com/robfig/cron
|
||||
version: 9585fd555638e77bba25f25db5c44b41f264aeb7
|
||||
- name: github.com/Sirupsen/logrus
|
||||
version: d26492970760ca5d33129d2d799e34be5c4782eb
|
||||
version: c078b1e43f58d563c74cebe63c85789e76ddb627
|
||||
- name: github.com/spf13/cobra
|
||||
version: a3c09249f1a24a9d951f2738fb9b9256b8b42fa5
|
||||
repo: https://github.com/dnephin/cobra.git
|
||||
version: 35136c09d8da66b901337c6e86fd8e88a1a255bd
|
||||
- name: github.com/spf13/pflag
|
||||
version: dabebe21bf790f782ea4c7bbd2efc430de182afd
|
||||
- name: github.com/stretchr/objx
|
||||
|
|
@ -157,7 +159,7 @@ imports:
|
|||
- tar/asm
|
||||
- tar/storage
|
||||
- name: golang.org/x/net
|
||||
version: 2beffdc2e92c8a3027590f898fe88f69af48a3f8
|
||||
version: ""
|
||||
repo: https://github.com/tonistiigi/net.git
|
||||
subpackages:
|
||||
- context
|
||||
|
|
@ -166,6 +168,7 @@ imports:
|
|||
- http2/hpack
|
||||
- internal/timeseries
|
||||
- proxy
|
||||
- publicsuffix
|
||||
- trace
|
||||
- name: golang.org/x/sys
|
||||
version: 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9
|
||||
|
|
|
|||
|
|
@ -26,3 +26,5 @@ import:
|
|||
- context
|
||||
- package: github.com/robfig/cron
|
||||
version: 9585fd555638e77bba25f25db5c44b41f264aeb7
|
||||
- package: github.com/mozillazg/request
|
||||
version: v0.8.0
|
||||
|
|
|
|||
34
main.go
34
main.go
|
|
@ -23,10 +23,11 @@ const DockerAPIMinVersion string = "1.24"
|
|||
var version = "master"
|
||||
|
||||
var (
|
||||
client container.Client
|
||||
scheduleSpec string
|
||||
cleanup bool
|
||||
noRestart bool
|
||||
client container.Client
|
||||
scheduleSpec string
|
||||
cleanup bool
|
||||
noRestart bool
|
||||
slackNotifier *actions.SlackNotifier
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -82,6 +83,15 @@ func main() {
|
|||
Name: "debug",
|
||||
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 {
|
||||
|
|
@ -108,6 +118,9 @@ func before(c *cli.Context) error {
|
|||
cleanup = c.GlobalBool("cleanup")
|
||||
noRestart = c.GlobalBool("no-restart")
|
||||
|
||||
slackURL := c.GlobalString("slack-hook-url")
|
||||
slackPrefix := c.GlobalString("slack-message-identification")
|
||||
|
||||
// configure environment vars for client
|
||||
err := envConfig(c)
|
||||
if err != nil {
|
||||
|
|
@ -115,6 +128,12 @@ func before(c *cli.Context) error {
|
|||
}
|
||||
|
||||
client = container.NewClient(!c.GlobalBool("no-pull"))
|
||||
|
||||
if len(slackURL) != 0 {
|
||||
slackNotifier = actions.NewSlackNotifier(slackURL, slackPrefix)
|
||||
slackNotifier.NotifyStartup()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -135,9 +154,14 @@ func start(c *cli.Context) error {
|
|||
select {
|
||||
case v := <-tryLockSem:
|
||||
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)
|
||||
}
|
||||
if slackNotifier != nil && (len(updatedContainers) != 0 || len(errorMessages) != 0) {
|
||||
slackNotifier.NotifyContainerUpdate(updatedContainers, errorMessages)
|
||||
|
||||
}
|
||||
default:
|
||||
log.Debug("Skipped another update already running.")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue