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 (
client container.Client
scheduleSpec string
cleanup bool
noRestart bool
monitorOnly bool
enableLabel bool
notifier t.Notifier
timeout time.Duration
lifecycleHooks bool
rollingRestart bool
scope string
labelPrecedence bool
client container.Client
scheduleSpec string
cleanup bool
noRestart bool
monitorOnly bool
enableLabel bool
disableContainers []string
notifier t.Notifier
timeout time.Duration
lifecycleHooks bool
rollingRestart bool
scope string
labelPrecedence bool
)
var rootCmd = NewRootCommand()
@ -93,6 +94,7 @@ func PreRun(cmd *cobra.Command, _ []string) {
}
enableLabel, _ = f.GetBool("label-enable")
disableContainers, _ = f.GetStringSlice("disable-containers")
lifecycleHooks, _ = f.GetBool("enable-lifecycle-hooks")
rollingRestart, _ = f.GetBool("rolling-restart")
scope, _ = f.GetString("scope")
@ -134,7 +136,7 @@ func PreRun(cmd *cobra.Command, _ []string) {
// Run is the main execution flow of the command
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")
enableUpdateAPI, _ := c.PersistentFlags().GetBool("http-api-update")
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
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
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

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"os"
"regexp"
"strings"
"time"
@ -85,6 +86,13 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
envBool("WATCHTOWER_LABEL_ENABLE"),
"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(
"log-format",
"l",
@ -198,7 +206,7 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
false,
"Do health check and exit")
flags.BoolP(
flags.BoolP(
"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
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 {
if len(names) == 0 {
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
func FilterByEnableLabel(baseFilter t.Filter) t.Filter {
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
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{}
filter := NoFilter
filter = FilterByNames(names, filter)
filter = FilterByDisableNames(disableNames, filter)
if len(names) > 0 {
sb.WriteString("which name matches \"")
@ -118,6 +135,16 @@ func BuildFilter(names []string, enableLabel bool, scope string) (t.Filter, stri
}
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 label filtering is enabled, containers should only be considered

@ -171,7 +171,7 @@ func TestFilterByImage(t *testing.T) {
func TestBuildFilter(t *testing.T) {
names := []string{"test", "valid"}
filter, desc := BuildFilter(names, false, "")
filter, desc := BuildFilter(names, []string{}, false, "")
assert.Contains(t, desc, "test")
assert.Contains(t, desc, "or")
assert.Contains(t, desc, "valid")
@ -210,7 +210,7 @@ func TestBuildFilterEnableLabel(t *testing.T) {
var names []string
names = append(names, "test")
filter, desc := BuildFilter(names, true, "")
filter, desc := BuildFilter(names, []string{}, true, "")
assert.Contains(t, desc, "using enable label")
container := new(mocks.FilterableContainer)
@ -235,3 +235,52 @@ func TestBuildFilterEnableLabel(t *testing.T) {
assert.False(t, filter(container))
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