Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 5 additions & 14 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ var (
lifecycleHooks bool
rollingRestart bool
scope string
// Set on build using ldflags
)

var rootCmd = NewRootCommand()
Expand Down Expand Up @@ -75,6 +74,7 @@ func Execute() {
// PreRun is a lifecycle hook that runs before the command is executed.
func PreRun(cmd *cobra.Command, _ []string) {
f := cmd.PersistentFlags()
flags.ProcessFlagAliases(f)

if enabled, _ := f.GetBool("no-color"); enabled {
log.SetFormatter(&log.TextFormatter{
Expand All @@ -94,18 +94,7 @@ func PreRun(cmd *cobra.Command, _ []string) {
log.SetLevel(log.TraceLevel)
}

pollingSet := f.Changed("interval")
schedule, _ := f.GetString("schedule")
cronLen := len(schedule)

if pollingSet && cronLen > 0 {
log.Fatal("Only schedule or interval can be defined, not both.")
} else if cronLen > 0 {
scheduleSpec, _ = f.GetString("schedule")
} else {
interval, _ := f.GetInt("interval")
scheduleSpec = "@every " + strconv.Itoa(interval) + "s"
}
scheduleSpec, _ = f.GetString("schedule")

flags.GetSecretsFromFiles(cmd)
cleanup, noRestart, monitorOnly, timeout = flags.ReadFlags(cmd)
Expand All @@ -119,7 +108,9 @@ func PreRun(cmd *cobra.Command, _ []string) {
rollingRestart, _ = f.GetBool("rolling-restart")
scope, _ = f.GetString("scope")

log.Debug(scope)
if scope != "" {
log.Debugf(`Using scope %q`, scope)
}

// configure environment vars for client
err := flags.EnvConfig(cmd)
Expand Down
67 changes: 67 additions & 0 deletions internal/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package flags

import (
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
Expand Down Expand Up @@ -152,22 +153,31 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
"",
viper.GetString("WATCHTOWER_HTTP_API_TOKEN"),
"Sets an authentication token to HTTP API requests.")

flags.BoolP(
"http-api-periodic-polls",
"",
viper.GetBool("WATCHTOWER_HTTP_API_PERIODIC_POLLS"),
"Also run periodic updates (specified with --interval and --schedule) if HTTP API is enabled")

// https://no-color.org/
flags.BoolP(
"no-color",
"",
viper.IsSet("NO_COLOR"),
"Disable ANSI color escape codes in log output")

flags.StringP(
"scope",
"",
viper.GetString("WATCHTOWER_SCOPE"),
"Defines a monitoring scope for the Watchtower instance.")

flags.BoolP(
"porcelain",
"P",
viper.GetBool("WATCHTOWER_PORCELAIN"),
"Write session results to stdout (alias for --notification-url logger:// --notification-log-stdout)")
}

// RegisterNotificationFlags that are used by watchtower to send notifications
Expand Down Expand Up @@ -342,6 +352,10 @@ Should only be used for testing.`)
viper.GetString("WATCHTOWER_WARN_ON_HEAD_FAILURE"),
"When to warn about HEAD pull requests failing. Possible values: always, auto or never")

flags.Bool(
"notification-log-stdout",
viper.GetBool("WATCHTOWER_NOTIFICATION_LOG_STDOUT"),
"Write notification logs to stdout instead of logging (to stderr)")
}

// SetDefaults provides default values for environment variables
Expand Down Expand Up @@ -479,3 +493,56 @@ func isFile(s string) bool {
_, err := os.Stat(s)
return !errors.Is(err, os.ErrNotExist)
}

// ProcessFlagAliases updates the value of flags that are being set by helper flags
func ProcessFlagAliases(flags *pflag.FlagSet) {

porcelain, err := flags.GetBool(`porcelain`)
if err != nil {
log.Fatalf(`Failed to get flag: %v`, err)
}
if porcelain {
if err = appendFlagValue(flags, `notification-url`, `logger://`); err != nil {
log.Errorf(`Failed to set flag: %v`, err)
}
setFlagIfDefault(flags, `notification-log-stdout`, `true`)
setFlagIfDefault(flags, `notification-report`, `true`)
setFlagIfDefault(flags, `notification-template`, `porcelain.v1.summary-no-log`)
}

if flags.Changed(`interval`) && flags.Changed(`schedule`) {
log.Fatal(`Only schedule or interval can be defined, not both.`)
}

// update schedule flag to match interval if it's set, or to the default if none of them are
if flags.Changed(`interval`) || !flags.Changed(`schedule`) {
interval, _ := flags.GetInt(`interval`)
flags.Set(`schedule`, fmt.Sprintf(`@every %ds`, interval))
}
}

func appendFlagValue(flags *pflag.FlagSet, name string, values ...string) error {
flag := flags.Lookup(name)
if flag == nil {
return fmt.Errorf(`invalid flag name %q`, name)
}

if flagValues, ok := flag.Value.(pflag.SliceValue); ok {
for _, value := range values {
flagValues.Append(value)
}
} else {
return fmt.Errorf(`the value for flag %q is not a slice value`, name)
}

return nil
}

func setFlagIfDefault(flags *pflag.FlagSet, name string, value string) {
if flags.Changed(name) {
return
}
if err := flags.Set(name, value); err != nil {
log.Errorf(`Failed to set flag: %v`, err)
}
}
50 changes: 50 additions & 0 deletions internal/flags/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"testing"

"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -103,3 +104,52 @@ func TestIsFile(t *testing.T) {
assert.False(t, isFile("https://google.com"), "an URL should never be considered a file")
assert.True(t, isFile(os.Args[0]), "the currently running binary path should always be considered a file")
}

func TestReadFlags(t *testing.T) {
logrus.StandardLogger().ExitFunc = func(_ int) { t.FailNow() }

}

func TestProcessFlagAliases(t *testing.T) {
logrus.StandardLogger().ExitFunc = func(_ int) { t.FailNow() }
cmd := new(cobra.Command)
SetDefaults()
RegisterDockerFlags(cmd)
RegisterSystemFlags(cmd)
RegisterNotificationFlags(cmd)

require.NoError(t, cmd.ParseFlags([]string{`--porcelain`, `--interval`, `10`}))
flags := cmd.Flags()
ProcessFlagAliases(flags)

urls, _ := flags.GetStringArray(`notification-url`)
assert.Contains(t, urls, `logger://`)

logStdout, _ := flags.GetBool(`notification-log-stdout`)
assert.True(t, logStdout)

report, _ := flags.GetBool(`notification-report`)
assert.True(t, report)

template, _ := flags.GetString(`notification-template`)
assert.Equal(t, `porcelain.v1.summary-no-log`, template)

sched, _ := flags.GetString(`schedule`)
assert.Equal(t, `@every 10s`, sched)
}

func TestProcessFlagAliasesSchedAndInterval(t *testing.T) {
logrus.StandardLogger().ExitFunc = func(_ int) { panic(`FATAL`) }
cmd := new(cobra.Command)
SetDefaults()
RegisterDockerFlags(cmd)
RegisterSystemFlags(cmd)
RegisterNotificationFlags(cmd)

require.NoError(t, cmd.ParseFlags([]string{`--schedule`, `@now`, `--interval`, `10`}))
flags := cmd.Flags()

assert.PanicsWithValue(t, `FATAL`, func() {
ProcessFlagAliases(flags)
})
}
36 changes: 36 additions & 0 deletions pkg/notifications/common_templates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package notifications

var commonTemplates = map[string]string{
`default-legacy`: "{{range .}}{{.Message}}{{println}}{{end}}",

`default`: `
{{- if .Report -}}
{{- with .Report -}}
{{- if ( or .Updated .Failed ) -}}
{{len .Scanned}} Scanned, {{len .Updated}} Updated, {{len .Failed}} Failed
{{- range .Updated}}
- {{.Name}} ({{.ImageName}}): {{.CurrentImageID.ShortID}} updated to {{.LatestImageID.ShortID}}
{{- end -}}
{{- range .Fresh}}
- {{.Name}} ({{.ImageName}}): {{.State}}
{{- end -}}
{{- range .Skipped}}
- {{.Name}} ({{.ImageName}}): {{.State}}: {{.Error}}
{{- end -}}
{{- range .Failed}}
- {{.Name}} ({{.ImageName}}): {{.State}}: {{.Error}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- else -}}
{{range .Entries -}}{{.Message}}{{"\n"}}{{- end -}}
{{- end -}}`,

`porcelain.v1.summary-no-log`: `
{{- if .Report -}}
{{len .Report.All}} containers matched filter{{println}}
{{- range .Report.All}}
{{- println}}{{.Name}} ({{.ImageName}}): {{.State}} {{- with .Error}} Error: {{.}}{{end}}
{{- end -}}
{{- end -}}`,
}
1 change: 0 additions & 1 deletion pkg/notifications/email.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const (
)

type emailTypeNotifier struct {
url string
From, To string
Server, User, Password, SubjectTag string
Port int
Expand Down
7 changes: 4 additions & 3 deletions pkg/notifications/notifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,21 @@ func NewNotifier(c *cobra.Command) ty.Notifier {
log.Fatalf("Notifications invalid log level: %s", err.Error())
}

acceptedLogLevels := slackrus.LevelThreshold(logLevel)
levels := slackrus.LevelThreshold(logLevel)
// slackrus does not allow log level TRACE, even though it's an accepted log level for logrus
if len(acceptedLogLevels) == 0 {
if len(levels) == 0 {
log.Fatalf("Unsupported notification log level provided: %s", level)
}

reportTemplate, _ := f.GetBool("notification-report")
stdout, _ := f.GetBool("notification-log-stdout")
tplString, _ := f.GetString("notification-template")
urls, _ := f.GetStringArray("notification-url")

data := GetTemplateData(c)
urls, delay := AppendLegacyUrls(urls, c, data.Title)

return newShoutrrrNotifier(tplString, acceptedLogLevels, !reportTemplate, data, delay, urls...)
return newShoutrrrNotifier(tplString, levels, !reportTemplate, data, delay, stdout, urls...)
}

// AppendLegacyUrls creates shoutrrr equivalent URLs from legacy notification flags
Expand Down
Loading