Monitor-only for individual containers (#652)

* Add monitor-only label

* Add tests for monitor-only

* Treat missing monitor-only label as if the option was set to false

* Add docs for container-based monitor-only

* Add function doc

* Fix monitor-only logic
pull/656/head
David H 4 years ago committed by GitHub
parent 98b518612b
commit bde421be0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -162,7 +162,7 @@ Environment Variable: WATCHTOWER_LABEL_ENABLE
**Do not** update containers that have `com.centurylinklabs.watchtower.enable` label set to false and 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.
## Without updating containers
Will only monitor for new images, not update the 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 containers.
> ### ⚠️ Please note
>
@ -175,6 +175,8 @@ Environment Variable: WATCHTOWER_MONITOR_ONLY
Default: false
```
Note that monitor-only can also be specified on a per-container basis with the `com.centurylinklabs.watchtower.monitor-only` label set on those containers.
## Without restarting containers
Do not restart containers after updating. This option can be useful when the start of the containers
is managed by an external system such as systemd.

@ -1,5 +1,12 @@
By default, watchtower will watch all containers. However, sometimes only some containers should be updated.
There are two options:
- **Fully exclude**: You can choose to exclude containers entirely from being watched by watchtower.
- **Monitor only**: In this mode, watchtower checks for container updates, sends notifications and invokes the [pre-check/post-check hooks](https://containrrr.dev/watchtower/lifecycle-hooks/) on the containers but does **not** perform the update.
## Full Exclude
If you need to exclude some containers, set the _com.centurylinklabs.watchtower.enable_ label to `false`.
```docker
@ -29,3 +36,23 @@ If you wish to create a monitoring scope, you will need to [run multiple instanc
Watchtower filters running containers by testing them against each configured criteria. A container is monitored if all criteria are met. For example:
- If a container's name is on the monitoring name list (not empty `--name` argument) but it is not enabled (_centurylinklabs.watchtower.enable=false_), it won't be monitored;
- If a container's name is not on the monitoring name list (not empty `--name` argument), even if it is enabled (_centurylinklabs.watchtower.enable=true_ and `--label-enable` flag is set), it won't be monitored;
## Monitor Only
Individual containers can be marked to only be monitored (without being updated).
To do so, set the *com.centurylinklabs.watchtower.monitor-only* label to `true` on that container.
```docker
LABEL com.centurylinklabs.watchtower.monitor-only="true"
```
Or, it can be specified as part of the `docker run` command line:
```bash
docker run -d --label=com.centurylinklabs.watchtower.monitor-only=true someimage
```
When the label is specified on a container, watchtower treats that container exactly as if [`WATCHTOWER_MONITOR_ONLY`](https://containrrr.dev/watchtower/arguments/#without_updating_containers) was set, but the effect is limited to the individual container.

@ -27,3 +27,22 @@ func CreateMockContainer(id string, name string, image string, created time.Time
},
)
}
// CreateMockContainerWithConfig creates a container substitute valid for testing
func CreateMockContainerWithConfig(id string, name string, image string, created time.Time, config *container2.Config) container.Container {
content := types.ContainerJSON{
ContainerJSONBase: &types.ContainerJSONBase{
ID: id,
Image: image,
Name: name,
Created: created.String(),
},
Config: config,
}
return *container.NewContainer(
&content,
&types.ImageInspect{
ID: image,
},
)
}

@ -28,7 +28,7 @@ func Update(client container.Client, params types.UpdateParams) error {
for i, targetContainer := range containers {
stale, err := client.IsContainerStale(targetContainer)
if stale && !params.NoRestart && !params.MonitorOnly && !targetContainer.HasImageInfo() {
if stale && !params.NoRestart && !params.MonitorOnly && !targetContainer.IsMonitorOnly() && !targetContainer.HasImageInfo() {
err = errors.New("no available image info")
}
if err != nil {
@ -45,18 +45,20 @@ func Update(client container.Client, params types.UpdateParams) error {
checkDependencies(containers)
if params.MonitorOnly {
if params.LifecycleHooks {
lifecycle.ExecutePostChecks(client, params)
containersToUpdate := []container.Container{}
if !params.MonitorOnly {
for i := len(containers) - 1; i >= 0; i-- {
if !containers[i].IsMonitorOnly() {
containersToUpdate = append(containersToUpdate, containers[i])
}
}
return nil
}
if params.RollingRestart {
performRollingRestart(containers, client, params)
performRollingRestart(containersToUpdate, client, params)
} else {
stopContainersInReversedOrder(containers, client, params)
restartContainersInSortedOrder(containers, client, params)
stopContainersInReversedOrder(containersToUpdate, client, params)
restartContainersInSortedOrder(containersToUpdate, client, params)
}
if params.LifecycleHooks {
lifecycle.ExecutePostChecks(client, params)

@ -5,6 +5,7 @@ import (
"github.com/containrrr/watchtower/pkg/container"
"github.com/containrrr/watchtower/pkg/container/mocks"
"github.com/containrrr/watchtower/pkg/types"
container2 "github.com/docker/docker/api/types/container"
cli "github.com/docker/docker/client"
"time"
@ -80,4 +81,73 @@ var _ = Describe("the update action", func() {
})
})
})
When("watchtower has been instructed to monitor only", func() {
When("certain containers are set to monitor only", func() {
BeforeEach(func() {
client = CreateMockClient(
&TestData{
NameOfContainerToKeep: "test-container-02",
Containers: []container.Container{
CreateMockContainer(
"test-container-01",
"test-container-01",
"fake-image1:latest",
time.Now()),
CreateMockContainerWithConfig(
"test-container-02",
"test-container-02",
"fake-image2:latest",
time.Now(),
&container2.Config{
Labels: map[string]string{
"com.centurylinklabs.watchtower.monitor-only": "true",
},
}),
},
},
dockerClient,
false,
false,
)
})
It("should not update those containers", func() {
err := actions.Update(client, types.UpdateParams{Cleanup: true})
Expect(err).NotTo(HaveOccurred())
Expect(client.TestData.TriedToRemoveImageCount).To(Equal(1))
})
})
When("monitor only is set globally", func() {
BeforeEach(func() {
client = CreateMockClient(
&TestData{
Containers: []container.Container{
CreateMockContainer(
"test-container-01",
"test-container-01",
"fake-image:latest",
time.Now()),
CreateMockContainer(
"test-container-02",
"test-container-02",
"fake-image:latest",
time.Now()),
},
},
dockerClient,
false,
false,
)
})
It("should not update any containers", func() {
err := actions.Update(client, types.UpdateParams{MonitorOnly: true})
Expect(err).NotTo(HaveOccurred())
Expect(client.TestData.TriedToRemoveImageCount).To(Equal(0))
})
})
})
})

@ -90,6 +90,22 @@ func (c Container) Enabled() (bool, bool) {
return parsedBool, true
}
// IsMonitorOnly returns the value of the monitor-only label. If the label
// is not set then false is returned.
func (c Container) IsMonitorOnly() bool {
rawBool, ok := c.getLabelValue(monitorOnlyLabel)
if !ok {
return false
}
parsedBool, err := strconv.ParseBool(rawBool)
if err != nil {
return false
}
return parsedBool
}
// Scope returns the value of the scope UID label and if the label
// was set.
func (c Container) Scope() (string, bool) {

@ -4,6 +4,7 @@ const (
watchtowerLabel = "com.centurylinklabs.watchtower"
signalLabel = "com.centurylinklabs.watchtower.stop-signal"
enableLabel = "com.centurylinklabs.watchtower.enable"
monitorOnlyLabel = "com.centurylinklabs.watchtower.monitor-only"
dependsOnLabel = "com.centurylinklabs.watchtower.depends-on"
zodiacLabel = "com.centurylinklabs.zodiac.original-image"
scope = "com.centurylinklabs.watchtower.scope"

Loading…
Cancel
Save