feat: add support for "none" scope (#1800)

pull/1810/head
nils måsén 1 year ago committed by GitHub
parent 40b8c77100
commit dd54055143
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -359,6 +359,11 @@ Environment Variable: WATCHTOWER_HTTP_API_PERIODIC_POLLS
Update containers that have a `com.centurylinklabs.watchtower.scope` label set with the same value as the given argument. Update containers that have a `com.centurylinklabs.watchtower.scope` label set with the same value as the given argument.
This enables [running multiple instances](https://containrrr.dev/watchtower/running-multiple-instances). This enables [running multiple instances](https://containrrr.dev/watchtower/running-multiple-instances).
!!! note "Filter by lack of scope"
If you want other instances of watchtower to ignore the scoped containers, set this argument to `none`.
When omitted, watchtower will update all containers regardless of scope.
```text ```text
Argument: --scope Argument: --scope
Environment Variable: WATCHTOWER_SCOPE Environment Variable: WATCHTOWER_SCOPE

@ -1,10 +1,11 @@
By default, Watchtower will clean up other instances and won't allow multiple instances running on the same Docker host or swarm. It is possible to override this behavior by defining a [scope](https://containrrr.github.io/watchtower/arguments/#filter_by_scope) to each running instance. By default, Watchtower will clean up other instances and won't allow multiple instances running on the same Docker host or swarm. It is possible to override this behavior by defining a [scope](https://containrrr.github.io/watchtower/arguments/#filter_by_scope) to each running instance.
Notice that: !!! note
- Multiple instances can't run with the same scope; - Multiple instances can't run with the same scope;
- An instance without a scope will clean up other running instances, even if they have a defined scope; - An instance without a scope will clean up other running instances, even if they have a defined scope;
- Supplying `none` as the scope will treat `com.centurylinklabs.watchtower.scope=none`, `com.centurylinklabs.watchtower.scope=` and the lack of a `com.centurylinklabs.watchtower.scope` label as the scope `none`. This effectly enables you to run both scoped and unscoped watchtower instances on the same machine.
To define an instance monitoring scope, use the `--scope` argument or the `WATCHTOWER_SCOPE` environment variable on startup and set the _com.centurylinklabs.watchtower.scope_ label with the same value for the containers you want to include in this instance's scope (including the instance itself). To define an instance monitoring scope, use the `--scope` argument or the `WATCHTOWER_SCOPE` environment variable on startup and set the `com.centurylinklabs.watchtower.scope` label with the same value for the containers you want to include in this instance's scope (including the instance itself).
For example, in a Docker Compose config file: For example, in a Docker Compose config file:
@ -12,16 +13,29 @@ For example, in a Docker Compose config file:
version: '3' version: '3'
services: services:
app-monitored-by-watchtower: app-with-scope:
image: myapps/monitored-by-watchtower image: myapps/monitored-by-watchtower
labels: labels: [ "com.centurylinklabs.watchtower.scope=myscope" ]
- "com.centurylinklabs.watchtower.scope=myscope"
watchtower: scoped-watchtower:
image: containrrr/watchtower image: containrrr/watchtower
volumes: volumes: [ "/var/run/docker.sock:/var/run/docker.sock" ]
- /var/run/docker.sock:/var/run/docker.sock
command: --interval 30 --scope myscope command: --interval 30 --scope myscope
labels: labels: [ "com.centurylinklabs.watchtower.scope=myscope" ]
- "com.centurylinklabs.watchtower.scope=myscope"
unscoped-app-a:
image: myapps/app-a
unscoped-app-b:
image: myapps/app-b
labels: [ "com.centurylinklabs.watchtower.scope=none" ]
unscoped-app-c:
image: myapps/app-b
labels: [ "com.centurylinklabs.watchtower.scope=" ]
unscoped-watchtower:
image: containrrr/watchtower
volumes: [ "/var/run/docker.sock:/var/run/docker.sock" ]
command: --interval 30 --scope none
``` ```

@ -86,13 +86,14 @@ func FilterByDisabledLabel(baseFilter t.Filter) t.Filter {
// FilterByScope returns all containers that belongs to a specific scope // FilterByScope returns all containers that belongs to a specific scope
func FilterByScope(scope string, baseFilter t.Filter) t.Filter { func FilterByScope(scope string, baseFilter t.Filter) t.Filter {
if scope == "" { return func(c t.FilterableContainer) bool {
return baseFilter containerScope, containerHasScope := c.Scope()
if !containerHasScope || containerScope == "" {
containerScope = "none"
} }
return func(c t.FilterableContainer) bool { if containerScope == scope {
containerScope, ok := c.Scope()
if ok && containerScope == scope {
return baseFilter(c) return baseFilter(c)
} }
@ -152,7 +153,13 @@ func BuildFilter(names []string, disableNames []string, enableLabel bool, scope
filter = FilterByEnableLabel(filter) filter = FilterByEnableLabel(filter)
sb.WriteString("using enable label, ") sb.WriteString("using enable label, ")
} }
if scope != "" {
if scope == "none" {
// If a scope has explicitly defined as "none", containers should only be considered
// if they do not have a scope defined, or if it's explicitly set to "none".
filter = FilterByScope(scope, filter)
sb.WriteString(`without a scope, "`)
} else if scope != "" {
// If a scope has been defined, containers should only be considered // If a scope has been defined, containers should only be considered
// if the scope is specifically set. // if the scope is specifically set.
filter = FilterByScope(scope, filter) filter = FilterByScope(scope, filter)

@ -111,6 +111,53 @@ func TestFilterByScope(t *testing.T) {
container.AssertExpectations(t) container.AssertExpectations(t)
} }
func TestFilterByNoneScope(t *testing.T) {
scope := "none"
filter := FilterByScope(scope, NoFilter)
assert.NotNil(t, filter)
container := new(mocks.FilterableContainer)
container.On("Scope").Return("anyscope", true)
assert.False(t, filter(container))
container.AssertExpectations(t)
container = new(mocks.FilterableContainer)
container.On("Scope").Return("", false)
assert.True(t, filter(container))
container.AssertExpectations(t)
container = new(mocks.FilterableContainer)
container.On("Scope").Return("", true)
assert.True(t, filter(container))
container.AssertExpectations(t)
container = new(mocks.FilterableContainer)
container.On("Scope").Return("none", true)
assert.True(t, filter(container))
container.AssertExpectations(t)
}
func TestBuildFilterNoneScope(t *testing.T) {
filter, desc := BuildFilter(nil, nil, false, "none")
assert.Contains(t, desc, "without a scope")
scoped := new(mocks.FilterableContainer)
scoped.On("Enabled").Return(false, false)
scoped.On("Scope").Return("anyscope", true)
unscoped := new(mocks.FilterableContainer)
unscoped.On("Enabled").Return(false, false)
unscoped.On("Scope").Return("", false)
assert.False(t, filter(scoped))
assert.True(t, filter(unscoped))
scoped.AssertExpectations(t)
unscoped.AssertExpectations(t)
}
func TestFilterByDisabledLabel(t *testing.T) { func TestFilterByDisabledLabel(t *testing.T) {
filter := FilterByDisabledLabel(NoFilter) filter := FilterByDisabledLabel(NoFilter)
assert.NotNil(t, filter) assert.NotNil(t, filter)

Loading…
Cancel
Save