test: check flag/docs consistency

This commit is contained in:
nils måsén 2023-09-16 16:29:14 +02:00
parent 36391b0ae7
commit 8457887f37
2 changed files with 149 additions and 60 deletions

View file

@ -23,9 +23,9 @@ var defaultInterval = int((time.Hour * 24).Seconds())
// RegisterDockerFlags that are used directly by the docker api client // RegisterDockerFlags that are used directly by the docker api client
func RegisterDockerFlags(rootCmd *cobra.Command) { func RegisterDockerFlags(rootCmd *cobra.Command) {
flags := rootCmd.PersistentFlags() flags := rootCmd.PersistentFlags()
flags.StringP("host", "H", viper.GetString("DOCKER_HOST"), "daemon socket to connect to") flags.StringP("host", "H", envString("DOCKER_HOST"), "daemon socket to connect to")
flags.BoolP("tlsverify", "v", viper.GetBool("DOCKER_TLS_VERIFY"), "use TLS and verify the remote") flags.BoolP("tlsverify", "v", envBool("DOCKER_TLS_VERIFY"), "use TLS and verify the remote")
flags.StringP("api-version", "a", viper.GetString("DOCKER_API_VERSION"), "api version to use by docker client") flags.StringP("api-version", "a", envString("DOCKER_API_VERSION"), "api version to use by docker client")
} }
// RegisterSystemFlags that are used by watchtower to modify the program flow // RegisterSystemFlags that are used by watchtower to modify the program flow
@ -34,132 +34,132 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
flags.IntP( flags.IntP(
"interval", "interval",
"i", "i",
viper.GetInt("WATCHTOWER_POLL_INTERVAL"), envInt("WATCHTOWER_POLL_INTERVAL"),
"Poll interval (in seconds)") "Poll interval (in seconds)")
flags.StringP( flags.StringP(
"schedule", "schedule",
"s", "s",
viper.GetString("WATCHTOWER_SCHEDULE"), envString("WATCHTOWER_SCHEDULE"),
"The cron expression which defines when to update") "The cron expression which defines when to update")
flags.DurationP( flags.DurationP(
"stop-timeout", "stop-timeout",
"t", "t",
viper.GetDuration("WATCHTOWER_TIMEOUT"), envDuration("WATCHTOWER_TIMEOUT"),
"Timeout before a container is forcefully stopped") "Timeout before a container is forcefully stopped")
flags.BoolP( flags.BoolP(
"no-pull", "no-pull",
"", "",
viper.GetBool("WATCHTOWER_NO_PULL"), envBool("WATCHTOWER_NO_PULL"),
"Do not pull any new images") "Do not pull any new images")
flags.BoolP( flags.BoolP(
"no-restart", "no-restart",
"", "",
viper.GetBool("WATCHTOWER_NO_RESTART"), envBool("WATCHTOWER_NO_RESTART"),
"Do not restart any containers") "Do not restart any containers")
flags.BoolP( flags.BoolP(
"no-startup-message", "no-startup-message",
"", "",
viper.GetBool("WATCHTOWER_NO_STARTUP_MESSAGE"), envBool("WATCHTOWER_NO_STARTUP_MESSAGE"),
"Prevents watchtower from sending a startup message") "Prevents watchtower from sending a startup message")
flags.BoolP( flags.BoolP(
"cleanup", "cleanup",
"c", "c",
viper.GetBool("WATCHTOWER_CLEANUP"), envBool("WATCHTOWER_CLEANUP"),
"Remove previously used images after updating") "Remove previously used images after updating")
flags.BoolP( flags.BoolP(
"remove-volumes", "remove-volumes",
"", "",
viper.GetBool("WATCHTOWER_REMOVE_VOLUMES"), envBool("WATCHTOWER_REMOVE_VOLUMES"),
"Remove attached volumes before updating") "Remove attached volumes before updating")
flags.BoolP( flags.BoolP(
"label-enable", "label-enable",
"e", "e",
viper.GetBool("WATCHTOWER_LABEL_ENABLE"), envBool("WATCHTOWER_LABEL_ENABLE"),
"Watch containers where the com.centurylinklabs.watchtower.enable label is true") "Watch containers where the com.centurylinklabs.watchtower.enable label is true")
flags.BoolP( flags.BoolP(
"debug", "debug",
"d", "d",
viper.GetBool("WATCHTOWER_DEBUG"), envBool("WATCHTOWER_DEBUG"),
"Enable debug mode with verbose logging") "Enable debug mode with verbose logging")
flags.BoolP( flags.BoolP(
"trace", "trace",
"", "",
viper.GetBool("WATCHTOWER_TRACE"), envBool("WATCHTOWER_TRACE"),
"Enable trace mode with very verbose logging - caution, exposes credentials") "Enable trace mode with very verbose logging - caution, exposes credentials")
flags.BoolP( flags.BoolP(
"monitor-only", "monitor-only",
"m", "m",
viper.GetBool("WATCHTOWER_MONITOR_ONLY"), envBool("WATCHTOWER_MONITOR_ONLY"),
"Will only monitor for new images, not update the containers") "Will only monitor for new images, not update the containers")
flags.BoolP( flags.BoolP(
"run-once", "run-once",
"R", "R",
viper.GetBool("WATCHTOWER_RUN_ONCE"), envBool("WATCHTOWER_RUN_ONCE"),
"Run once now and exit") "Run once now and exit")
flags.BoolP( flags.BoolP(
"include-restarting", "include-restarting",
"", "",
viper.GetBool("WATCHTOWER_INCLUDE_RESTARTING"), envBool("WATCHTOWER_INCLUDE_RESTARTING"),
"Will also include restarting containers") "Will also include restarting containers")
flags.BoolP( flags.BoolP(
"include-stopped", "include-stopped",
"S", "S",
viper.GetBool("WATCHTOWER_INCLUDE_STOPPED"), envBool("WATCHTOWER_INCLUDE_STOPPED"),
"Will also include created and exited containers") "Will also include created and exited containers")
flags.BoolP( flags.BoolP(
"revive-stopped", "revive-stopped",
"", "",
viper.GetBool("WATCHTOWER_REVIVE_STOPPED"), envBool("WATCHTOWER_REVIVE_STOPPED"),
"Will also start stopped containers that were updated, if include-stopped is active") "Will also start stopped containers that were updated, if include-stopped is active")
flags.BoolP( flags.BoolP(
"enable-lifecycle-hooks", "enable-lifecycle-hooks",
"", "",
viper.GetBool("WATCHTOWER_LIFECYCLE_HOOKS"), envBool("WATCHTOWER_LIFECYCLE_HOOKS"),
"Enable the execution of commands triggered by pre- and post-update lifecycle hooks") "Enable the execution of commands triggered by pre- and post-update lifecycle hooks")
flags.BoolP( flags.BoolP(
"rolling-restart", "rolling-restart",
"", "",
viper.GetBool("WATCHTOWER_ROLLING_RESTART"), envBool("WATCHTOWER_ROLLING_RESTART"),
"Restart containers one at a time") "Restart containers one at a time")
flags.BoolP( flags.BoolP(
"http-api-update", "http-api-update",
"", "",
viper.GetBool("WATCHTOWER_HTTP_API_UPDATE"), envBool("WATCHTOWER_HTTP_API_UPDATE"),
"Runs Watchtower in HTTP API mode, so that image updates must to be triggered by a request") "Runs Watchtower in HTTP API mode, so that image updates must to be triggered by a request")
flags.BoolP( flags.BoolP(
"http-api-metrics", "http-api-metrics",
"", "",
viper.GetBool("WATCHTOWER_HTTP_API_METRICS"), envBool("WATCHTOWER_HTTP_API_METRICS"),
"Runs Watchtower with the Prometheus metrics API enabled") "Runs Watchtower with the Prometheus metrics API enabled")
flags.StringP( flags.StringP(
"http-api-token", "http-api-token",
"", "",
viper.GetString("WATCHTOWER_HTTP_API_TOKEN"), envString("WATCHTOWER_HTTP_API_TOKEN"),
"Sets an authentication token to HTTP API requests.") "Sets an authentication token to HTTP API requests.")
flags.BoolP( flags.BoolP(
"http-api-periodic-polls", "http-api-periodic-polls",
"", "",
viper.GetBool("WATCHTOWER_HTTP_API_PERIODIC_POLLS"), envBool("WATCHTOWER_HTTP_API_PERIODIC_POLLS"),
"Also run periodic updates (specified with --interval and --schedule) if HTTP API is enabled") "Also run periodic updates (specified with --interval and --schedule) if HTTP API is enabled")
// https://no-color.org/ // https://no-color.org/
@ -172,18 +172,18 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
flags.StringP( flags.StringP(
"scope", "scope",
"", "",
viper.GetString("WATCHTOWER_SCOPE"), envString("WATCHTOWER_SCOPE"),
"Defines a monitoring scope for the Watchtower instance.") "Defines a monitoring scope for the Watchtower instance.")
flags.StringP( flags.StringP(
"porcelain", "porcelain",
"P", "P",
viper.GetString("WATCHTOWER_PORCELAIN"), envString("WATCHTOWER_PORCELAIN"),
`Write session results to stdout using a stable versioned format. Supported values: "v1"`) `Write session results to stdout using a stable versioned format. Supported values: "v1"`)
flags.String( flags.String(
"log-level", "log-level",
viper.GetString("WATCHTOWER_LOG_LEVEL"), envString("WATCHTOWER_LOG_LEVEL"),
"The maximum log level that will be written to STDERR. Possible values: panic, fatal, error, warn, info, debug or trace") "The maximum log level that will be written to STDERR. Possible values: panic, fatal, error, warn, info, debug or trace")
} }
@ -194,177 +194,202 @@ func RegisterNotificationFlags(rootCmd *cobra.Command) {
flags.StringSliceP( flags.StringSliceP(
"notifications", "notifications",
"n", "n",
viper.GetStringSlice("WATCHTOWER_NOTIFICATIONS"), envStringSlice("WATCHTOWER_NOTIFICATIONS"),
" Notification types to send (valid: email, slack, msteams, gotify, shoutrrr)") " Notification types to send (valid: email, slack, msteams, gotify, shoutrrr)")
flags.String( flags.String(
"notifications-level", "notifications-level",
viper.GetString("WATCHTOWER_NOTIFICATIONS_LEVEL"), envString("WATCHTOWER_NOTIFICATIONS_LEVEL"),
"The log level used for sending notifications. Possible values: panic, fatal, error, warn, info or debug") "The log level used for sending notifications. Possible values: panic, fatal, error, warn, info or debug")
flags.IntP( flags.IntP(
"notifications-delay", "notifications-delay",
"", "",
viper.GetInt("WATCHTOWER_NOTIFICATIONS_DELAY"), envInt("WATCHTOWER_NOTIFICATIONS_DELAY"),
"Delay before sending notifications, expressed in seconds") "Delay before sending notifications, expressed in seconds")
flags.StringP( flags.StringP(
"notifications-hostname", "notifications-hostname",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATIONS_HOSTNAME"), envString("WATCHTOWER_NOTIFICATIONS_HOSTNAME"),
"Custom hostname for notification titles") "Custom hostname for notification titles")
flags.StringP( flags.StringP(
"notification-email-from", "notification-email-from",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_FROM"), envString("WATCHTOWER_NOTIFICATION_EMAIL_FROM"),
"Address to send notification emails from") "Address to send notification emails from")
flags.StringP( flags.StringP(
"notification-email-to", "notification-email-to",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_TO"), envString("WATCHTOWER_NOTIFICATION_EMAIL_TO"),
"Address to send notification emails to") "Address to send notification emails to")
flags.IntP( flags.IntP(
"notification-email-delay", "notification-email-delay",
"", "",
viper.GetInt("WATCHTOWER_NOTIFICATION_EMAIL_DELAY"), envInt("WATCHTOWER_NOTIFICATION_EMAIL_DELAY"),
"Delay before sending notifications, expressed in seconds") "Delay before sending notifications, expressed in seconds")
flags.StringP( flags.StringP(
"notification-email-server", "notification-email-server",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER"), envString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER"),
"SMTP server to send notification emails through") "SMTP server to send notification emails through")
flags.IntP( flags.IntP(
"notification-email-server-port", "notification-email-server-port",
"", "",
viper.GetInt("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT"), envInt("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT"),
"SMTP server port to send notification emails through") "SMTP server port to send notification emails through")
flags.BoolP( flags.BoolP(
"notification-email-server-tls-skip-verify", "notification-email-server-tls-skip-verify",
"", "",
viper.GetBool("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_TLS_SKIP_VERIFY"), envBool("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_TLS_SKIP_VERIFY"),
`Controls whether watchtower verifies the SMTP server's certificate chain and host name. `Controls whether watchtower verifies the SMTP server's certificate chain and host name.
Should only be used for testing.`) Should only be used for testing.`)
flags.StringP( flags.StringP(
"notification-email-server-user", "notification-email-server-user",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER"), envString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER"),
"SMTP server user for sending notifications") "SMTP server user for sending notifications")
flags.StringP( flags.StringP(
"notification-email-server-password", "notification-email-server-password",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD"), envString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD"),
"SMTP server password for sending notifications") "SMTP server password for sending notifications")
flags.StringP( flags.StringP(
"notification-email-subjecttag", "notification-email-subjecttag",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SUBJECTTAG"), envString("WATCHTOWER_NOTIFICATION_EMAIL_SUBJECTTAG"),
"Subject prefix tag for notifications via mail") "Subject prefix tag for notifications via mail")
flags.StringP( flags.StringP(
"notification-slack-hook-url", "notification-slack-hook-url",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL"), envString("WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL"),
"The Slack Hook URL to send notifications to") "The Slack Hook URL to send notifications to")
flags.StringP( flags.StringP(
"notification-slack-identifier", "notification-slack-identifier",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER"), envString("WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER"),
"A string which will be used to identify the messages coming from this watchtower instance") "A string which will be used to identify the messages coming from this watchtower instance")
flags.StringP( flags.StringP(
"notification-slack-channel", "notification-slack-channel",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_CHANNEL"), envString("WATCHTOWER_NOTIFICATION_SLACK_CHANNEL"),
"A string which overrides the webhook's default channel. Example: #my-custom-channel") "A string which overrides the webhook's default channel. Example: #my-custom-channel")
flags.StringP( flags.StringP(
"notification-slack-icon-emoji", "notification-slack-icon-emoji",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_ICON_EMOJI"), envString("WATCHTOWER_NOTIFICATION_SLACK_ICON_EMOJI"),
"An emoji code string to use in place of the default icon") "An emoji code string to use in place of the default icon")
flags.StringP( flags.StringP(
"notification-slack-icon-url", "notification-slack-icon-url",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_ICON_URL"), envString("WATCHTOWER_NOTIFICATION_SLACK_ICON_URL"),
"An icon image URL string to use in place of the default icon") "An icon image URL string to use in place of the default icon")
flags.StringP( flags.StringP(
"notification-msteams-hook", "notification-msteams-hook",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL"), envString("WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL"),
"The MSTeams WebHook URL to send notifications to") "The MSTeams WebHook URL to send notifications to")
flags.BoolP( flags.BoolP(
"notification-msteams-data", "notification-msteams-data",
"", "",
viper.GetBool("WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA"), envBool("WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA"),
"The MSTeams notifier will try to extract log entry fields as MSTeams message facts") "The MSTeams notifier will try to extract log entry fields as MSTeams message facts")
flags.StringP( flags.StringP(
"notification-gotify-url", "notification-gotify-url",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATION_GOTIFY_URL"), envString("WATCHTOWER_NOTIFICATION_GOTIFY_URL"),
"The Gotify URL to send notifications to") "The Gotify URL to send notifications to")
flags.StringP( flags.StringP(
"notification-gotify-token", "notification-gotify-token",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN"), envString("WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN"),
"The Gotify Application required to query the Gotify API") "The Gotify Application required to query the Gotify API")
flags.BoolP( flags.BoolP(
"notification-gotify-tls-skip-verify", "notification-gotify-tls-skip-verify",
"", "",
viper.GetBool("WATCHTOWER_NOTIFICATION_GOTIFY_TLS_SKIP_VERIFY"), envBool("WATCHTOWER_NOTIFICATION_GOTIFY_TLS_SKIP_VERIFY"),
`Controls whether watchtower verifies the Gotify server's certificate chain and host name. `Controls whether watchtower verifies the Gotify server's certificate chain and host name.
Should only be used for testing.`) Should only be used for testing.`)
flags.String( flags.String(
"notification-template", "notification-template",
viper.GetString("WATCHTOWER_NOTIFICATION_TEMPLATE"), envString("WATCHTOWER_NOTIFICATION_TEMPLATE"),
"The shoutrrr text/template for the messages") "The shoutrrr text/template for the messages")
flags.StringArray( flags.StringArray(
"notification-url", "notification-url",
viper.GetStringSlice("WATCHTOWER_NOTIFICATION_URL"), envStringSlice("WATCHTOWER_NOTIFICATION_URL"),
"The shoutrrr URL to send notifications to") "The shoutrrr URL to send notifications to")
flags.Bool("notification-report", flags.Bool("notification-report",
viper.GetBool("WATCHTOWER_NOTIFICATION_REPORT"), envBool("WATCHTOWER_NOTIFICATION_REPORT"),
"Use the session report as the notification template data") "Use the session report as the notification template data")
flags.StringP( flags.StringP(
"notification-title-tag", "notification-title-tag",
"", "",
viper.GetString("WATCHTOWER_NOTIFICATION_TITLE_TAG"), envString("WATCHTOWER_NOTIFICATION_TITLE_TAG"),
"Title prefix tag for notifications") "Title prefix tag for notifications")
flags.Bool("notification-skip-title", flags.Bool("notification-skip-title",
viper.GetBool("WATCHTOWER_NOTIFICATION_SKIP_TITLE"), envBool("WATCHTOWER_NOTIFICATION_SKIP_TITLE"),
"Do not pass the title param to notifications") "Do not pass the title param to notifications")
flags.String( flags.String(
"warn-on-head-failure", "warn-on-head-failure",
viper.GetString("WATCHTOWER_WARN_ON_HEAD_FAILURE"), envString("WATCHTOWER_WARN_ON_HEAD_FAILURE"),
"When to warn about HEAD pull requests failing. Possible values: always, auto or never") "When to warn about HEAD pull requests failing. Possible values: always, auto or never")
flags.Bool( flags.Bool(
"notification-log-stdout", "notification-log-stdout",
viper.GetBool("WATCHTOWER_NOTIFICATION_LOG_STDOUT"), envBool("WATCHTOWER_NOTIFICATION_LOG_STDOUT"),
"Write notification logs to stdout instead of logging (to stderr)") "Write notification logs to stdout instead of logging (to stderr)")
} }
func envString(key string) string {
viper.MustBindEnv(key)
return viper.GetString(key)
}
func envStringSlice(key string) []string {
viper.MustBindEnv(key)
return viper.GetStringSlice(key)
}
func envInt(key string) int {
viper.MustBindEnv(key)
return viper.GetInt(key)
}
func envBool(key string) bool {
viper.MustBindEnv(key)
return viper.GetBool(key)
}
func envDuration(key string) time.Duration {
viper.MustBindEnv(key)
return viper.GetDuration(key)
}
// SetDefaults provides default values for environment variables // SetDefaults provides default values for environment variables
func SetDefaults() { func SetDefaults() {
viper.AutomaticEnv() viper.AutomaticEnv()

View file

@ -1,12 +1,16 @@
package flags package flags
import ( import (
"os"
"strings"
"testing"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"os"
"testing"
) )
func TestEnvConfig_Defaults(t *testing.T) { func TestEnvConfig_Defaults(t *testing.T) {
@ -222,3 +226,63 @@ func TestProcessFlagAliasesInvalidPorcelaineVersion(t *testing.T) {
ProcessFlagAliases(flags) ProcessFlagAliases(flags)
}) })
} }
func TestFlagsArePrecentInDocumentation(t *testing.T) {
// Legacy notifcations are ignored, since they are (soft) deprecated
ignoredEnvs := map[string]string{
"WATCHTOWER_NOTIFICATION_SLACK_ICON_EMOJI": "legacy",
"WATCHTOWER_NOTIFICATION_SLACK_ICON_URL": "legacy",
}
ignoredFlags := map[string]string{
"notification-gotify-url": "legacy",
"notification-slack-icon-emoji": "legacy",
"notification-slack-icon-url": "legacy",
}
cmd := new(cobra.Command)
SetDefaults()
RegisterDockerFlags(cmd)
RegisterSystemFlags(cmd)
RegisterNotificationFlags(cmd)
flags := cmd.PersistentFlags()
docFiles := []string{
"../../docs/arguments.md",
"../../docs/lifecycle-hooks.md",
"../../docs/notifications.md",
}
allDocs := ""
for _, f := range docFiles {
bytes, err := os.ReadFile(f)
if err != nil {
t.Fatalf("Could not load docs file %q: %v", f, err)
}
allDocs += string(bytes)
}
flags.VisitAll(func(f *pflag.Flag) {
if !strings.Contains(allDocs, f.Name) {
if _, found := ignoredFlags[f.Name]; !found {
t.Logf("Docs does not mention flag long name %q", f.Name)
t.Fail()
}
}
if !strings.Contains(allDocs, "-"+f.Shorthand) {
t.Logf("Docs does not mention flag shorthand %q (%q)", f.Shorthand, f.Name)
t.Fail()
}
})
for _, key := range viper.AllKeys() {
envKey := strings.ToUpper(key)
if !strings.Contains(allDocs, envKey) {
if _, found := ignoredEnvs[envKey]; !found {
t.Logf("Docs does not mention environment variable %q", envKey)
t.Fail()
}
}
}
}