Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
28 changes: 15 additions & 13 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down
13 changes: 13 additions & 0 deletions docs/arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 10 additions & 2 deletions internal/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"os"
"regexp"
"strings"
"time"

Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -197,8 +205,8 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
"",
false,
"Do health check and exit")
flags.BoolP(

flags.BoolP(
"label-take-precedence",
"",
envBool("WATCHTOWER_LABEL_TAKE_PRECEDENCE"),
Expand Down
31 changes: 29 additions & 2 deletions pkg/filters/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 \"")
Expand All @@ -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
Expand Down
53 changes: 51 additions & 2 deletions pkg/filters/filters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand All @@ -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