feat(filters): Add a flag/env to explicitly exclude containers by name (#1784)

pull/1791/head
Rodrigo Damazio Bovendorp 1 year ago committed by GitHub
parent 9180e9558e
commit 623f4e67fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -29,18 +29,19 @@ import (
) )
var ( var (
client container.Client client container.Client
scheduleSpec string scheduleSpec string
cleanup bool cleanup bool
noRestart bool noRestart bool
monitorOnly bool monitorOnly bool
enableLabel bool enableLabel bool
notifier t.Notifier disableContainers []string
timeout time.Duration notifier t.Notifier
lifecycleHooks bool timeout time.Duration
rollingRestart bool lifecycleHooks bool
scope string rollingRestart bool
labelPrecedence bool scope string
labelPrecedence bool
) )
var rootCmd = NewRootCommand() var rootCmd = NewRootCommand()
@ -93,6 +94,7 @@ func PreRun(cmd *cobra.Command, _ []string) {
} }
enableLabel, _ = f.GetBool("label-enable") enableLabel, _ = f.GetBool("label-enable")
disableContainers, _ = f.GetStringSlice("disable-containers")
lifecycleHooks, _ = f.GetBool("enable-lifecycle-hooks") lifecycleHooks, _ = f.GetBool("enable-lifecycle-hooks")
rollingRestart, _ = f.GetBool("rolling-restart") rollingRestart, _ = f.GetBool("rolling-restart")
scope, _ = f.GetString("scope") scope, _ = f.GetString("scope")
@ -134,7 +136,7 @@ func PreRun(cmd *cobra.Command, _ []string) {
// Run is the main execution flow of the command // Run is the main execution flow of the command
func Run(c *cobra.Command, names []string) { func Run(c *cobra.Command, names []string) {
filter, filterDesc := filters.BuildFilter(names, enableLabel, scope) filter, filterDesc := filters.BuildFilter(names, disableContainers, enableLabel, scope)
runOnce, _ := c.PersistentFlags().GetBool("run-once") runOnce, _ := c.PersistentFlags().GetBool("run-once")
enableUpdateAPI, _ := c.PersistentFlags().GetBool("http-api-update") enableUpdateAPI, _ := c.PersistentFlags().GetBool("http-api-update")
enableMetricsAPI, _ := c.PersistentFlags().GetBool("http-api-metrics") enableMetricsAPI, _ := c.PersistentFlags().GetBool("http-api-metrics")

@ -230,6 +230,19 @@ __Do not__ Monitor and update containers that have `com.centurylinklabs.watchtow
no `--label-enable` argument is passed. Note that only one or the other (targeting by enable label) can be no `--label-enable` argument is passed. Note that only one or the other (targeting by enable label) can be
used at the same time to target containers. used at the same time to target containers.
## Filter by disabling specific container names
Monitor and update containers whose names are not in a given set of names.
This can be used to exclude specific containers, when setting labels is not an option.
The listed containers will be excluded even if they have the enable filter set to true.
```text
Argument: --disable-containers, -x
Environment Variable: WATCHTOWER_DISABLE_CONTAINERS
Type: Comma- or space-separated string list
Default: ""
```
## Without updating containers ## Without updating containers
Will only monitor for new images, send notifications and invoke Will only monitor for new images, send notifications and invoke
the [pre-check/post-check hooks](https://containrrr.dev/watchtower/lifecycle-hooks/), but will __not__ update the the [pre-check/post-check hooks](https://containrrr.dev/watchtower/lifecycle-hooks/), but will __not__ update the

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"regexp"
"strings" "strings"
"time" "time"
@ -85,6 +86,13 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
envBool("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.StringSliceP(
"disable-containers",
"x",
// Due to issue spf13/viper#380, can't use viper.GetStringSlice:
regexp.MustCompile("[, ]+").Split(envString("WATCHTOWER_DISABLE_CONTAINERS"), -1),
"Comma-separated list of containers to explicitly exclude from watching.")
flags.StringP( flags.StringP(
"log-format", "log-format",
"l", "l",
@ -198,7 +206,7 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
false, false,
"Do health check and exit") "Do health check and exit")
flags.BoolP( flags.BoolP(
"label-take-precedence", "label-take-precedence",
"", "",
envBool("WATCHTOWER_LABEL_TAKE_PRECEDENCE"), envBool("WATCHTOWER_LABEL_TAKE_PRECEDENCE"),

@ -13,7 +13,7 @@ func WatchtowerContainersFilter(c t.FilterableContainer) bool { return c.IsWatch
// NoFilter will not filter out any containers // NoFilter will not filter out any containers
func NoFilter(t.FilterableContainer) bool { return true } func NoFilter(t.FilterableContainer) bool { return true }
// FilterByNames returns all containers that match the specified name // FilterByNames returns all containers that match one of the specified names
func FilterByNames(names []string, baseFilter t.Filter) t.Filter { func FilterByNames(names []string, baseFilter t.Filter) t.Filter {
if len(names) == 0 { if len(names) == 0 {
return baseFilter return baseFilter
@ -41,6 +41,22 @@ func FilterByNames(names []string, baseFilter t.Filter) t.Filter {
} }
} }
// FilterByDisableNames returns all containers that don't match any of the specified names
func FilterByDisableNames(disableNames []string, baseFilter t.Filter) t.Filter {
if len(disableNames) == 0 {
return baseFilter
}
return func(c t.FilterableContainer) bool {
for _, name := range disableNames {
if name == c.Name() || name == c.Name()[1:] {
return false
}
}
return baseFilter(c)
}
}
// FilterByEnableLabel returns all containers that have the enabled label set // FilterByEnableLabel returns all containers that have the enabled label set
func FilterByEnableLabel(baseFilter t.Filter) t.Filter { func FilterByEnableLabel(baseFilter t.Filter) t.Filter {
return func(c t.FilterableContainer) bool { return func(c t.FilterableContainer) bool {
@ -103,10 +119,11 @@ func FilterByImage(images []string, baseFilter t.Filter) t.Filter {
} }
// BuildFilter creates the needed filter of containers // BuildFilter creates the needed filter of containers
func BuildFilter(names []string, enableLabel bool, scope string) (t.Filter, string) { func BuildFilter(names []string, disableNames []string, enableLabel bool, scope string) (t.Filter, string) {
sb := strings.Builder{} sb := strings.Builder{}
filter := NoFilter filter := NoFilter
filter = FilterByNames(names, filter) filter = FilterByNames(names, filter)
filter = FilterByDisableNames(disableNames, filter)
if len(names) > 0 { if len(names) > 0 {
sb.WriteString("which name matches \"") sb.WriteString("which name matches \"")
@ -118,6 +135,16 @@ func BuildFilter(names []string, enableLabel bool, scope string) (t.Filter, stri
} }
sb.WriteString(`", `) sb.WriteString(`", `)
} }
if len(disableNames) > 0 {
sb.WriteString("not named one of \"")
for i, n := range disableNames {
sb.WriteString(n)
if i < len(disableNames)-1 {
sb.WriteString(`" or "`)
}
}
sb.WriteString(`", `)
}
if enableLabel { if enableLabel {
// If label filtering is enabled, containers should only be considered // If label filtering is enabled, containers should only be considered

@ -171,7 +171,7 @@ func TestFilterByImage(t *testing.T) {
func TestBuildFilter(t *testing.T) { func TestBuildFilter(t *testing.T) {
names := []string{"test", "valid"} names := []string{"test", "valid"}
filter, desc := BuildFilter(names, false, "") filter, desc := BuildFilter(names, []string{}, false, "")
assert.Contains(t, desc, "test") assert.Contains(t, desc, "test")
assert.Contains(t, desc, "or") assert.Contains(t, desc, "or")
assert.Contains(t, desc, "valid") assert.Contains(t, desc, "valid")
@ -210,7 +210,7 @@ func TestBuildFilterEnableLabel(t *testing.T) {
var names []string var names []string
names = append(names, "test") names = append(names, "test")
filter, desc := BuildFilter(names, true, "") filter, desc := BuildFilter(names, []string{}, true, "")
assert.Contains(t, desc, "using enable label") assert.Contains(t, desc, "using enable label")
container := new(mocks.FilterableContainer) container := new(mocks.FilterableContainer)
@ -235,3 +235,52 @@ func TestBuildFilterEnableLabel(t *testing.T) {
assert.False(t, filter(container)) assert.False(t, filter(container))
container.AssertExpectations(t) container.AssertExpectations(t)
} }
func TestBuildFilterDisableContainer(t *testing.T) {
filter, desc := BuildFilter([]string{}, []string{"excluded", "notfound"}, false, "")
assert.Contains(t, desc, "not named")
assert.Contains(t, desc, "excluded")
assert.Contains(t, desc, "or")
assert.Contains(t, desc, "notfound")
container := new(mocks.FilterableContainer)
container.On("Name").Return("Another")
container.On("Enabled").Return(false, false)
assert.True(t, filter(container))
container.AssertExpectations(t)
container = new(mocks.FilterableContainer)
container.On("Name").Return("AnotherOne")
container.On("Enabled").Return(true, true)
assert.True(t, filter(container))
container.AssertExpectations(t)
container = new(mocks.FilterableContainer)
container.On("Name").Return("test")
container.On("Enabled").Return(false, false)
assert.True(t, filter(container))
container.AssertExpectations(t)
container = new(mocks.FilterableContainer)
container.On("Name").Return("excluded")
container.On("Enabled").Return(true, true)
assert.False(t, filter(container))
container.AssertExpectations(t)
container = new(mocks.FilterableContainer)
container.On("Name").Return("excludedAsSubstring")
container.On("Enabled").Return(true, true)
assert.True(t, filter(container))
container.AssertExpectations(t)
container = new(mocks.FilterableContainer)
container.On("Name").Return("notfound")
container.On("Enabled").Return(true, true)
assert.False(t, filter(container))
container.AssertExpectations(t)
container = new(mocks.FilterableContainer)
container.On("Enabled").Return(false, true)
assert.False(t, filter(container))
container.AssertExpectations(t)
}

Loading…
Cancel
Save