Skip to content
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
39 changes: 21 additions & 18 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,18 @@ 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
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
)

var rootCmd = NewRootCommand()
Expand Down Expand Up @@ -109,6 +110,7 @@ func PreRun(cmd *cobra.Command, _ []string) {
lifecycleHooks, _ = f.GetBool("enable-lifecycle-hooks")
rollingRestart, _ = f.GetBool("rolling-restart")
scope, _ = f.GetString("scope")
labelPrecedence, _ = f.GetBool("label-take-precedence")

if scope != "" {
log.Debugf(`Using scope %q`, scope)
Expand Down Expand Up @@ -359,13 +361,14 @@ func runUpgradesOnSchedule(c *cobra.Command, filter t.Filter, filtering string,
func runUpdatesWithNotifications(filter t.Filter) *metrics.Metric {
notifier.StartNotification()
updateParams := t.UpdateParams{
Filter: filter,
Cleanup: cleanup,
NoRestart: noRestart,
Timeout: timeout,
MonitorOnly: monitorOnly,
LifecycleHooks: lifecycleHooks,
RollingRestart: rollingRestart,
Filter: filter,
Cleanup: cleanup,
NoRestart: noRestart,
Timeout: timeout,
MonitorOnly: monitorOnly,
LifecycleHooks: lifecycleHooks,
RollingRestart: rollingRestart,
LabelPrecedence: labelPrecedence,
}
result, err := actions.Update(client, updateParams)
if err != nil {
Expand Down
19 changes: 17 additions & 2 deletions docs/arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ Environment Variable: WATCHTOWER_POLL_INTERVAL
```

## Filter by enable label
Update containers that have a `com.centurylinklabs.watchtower.enable` label set to true.
Monitor and update containers that have a `com.centurylinklabs.watchtower.enable` label set to true.

```text
Argument: --label-enable
Expand All @@ -215,7 +215,7 @@ Environment Variable: WATCHTOWER_LABEL_ENABLE
```

## Filter by disable label
__Do not__ update containers that have `com.centurylinklabs.watchtower.enable` label set to false and
__Do not__ Monitor and 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.

Expand All @@ -238,6 +238,19 @@ Environment Variable: WATCHTOWER_MONITOR_ONLY

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.

See [With label taking precedence over arguments](#With-label-taking-precedence-over-arguments) for behavior when both argument and label are set

## With label taking precedence over arguments

By default, arguments will take precedence over labels. This means that if you set `WATCHTOWER_MONITOR_ONLY` to true or use `--monitor-only`, a container with `com.centurylinklabs.watchtower.monitor-only` set to false will not be updated. If you set `WATCHTOWER_LABEL_TAKE_PRECEDENCE` to true or use `--label-take-precedence`, then the container will also be updated. This also apply to the no pull option. if you set `WATCHTOWER_NO_PULL` to true or use `--no-pull`, a container with `com.centurylinklabs.watchtower.no-pull` set to false will not pull the new image. If you set `WATCHTOWER_LABEL_TAKE_PRECEDENCE` to true or use `--label-take-precedence`, then the container will pull image

```text
Argument: --label-take-precedence
Environment Variable: WATCHTOWER_LABEL_TAKE_PRECEDENCE
Type: Boolean
Default: false
```

## 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.
Expand All @@ -264,6 +277,8 @@ Environment Variable: WATCHTOWER_NO_PULL
Note that no-pull can also be specified on a per-container basis with the
`com.centurylinklabs.watchtower.no-pull` label set on those containers.

See [With label taking precedence over arguments](#With-label-taking-precedence-over-arguments) for behavior when both argument and label are set

## Without sending a startup message
Do not send a message after watchtower started. Otherwise there will be an info-level notification.

Expand Down
2 changes: 1 addition & 1 deletion internal/actions/mocks/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func (client MockClient) ExecuteCommand(_ t.ContainerID, command string, _ int)
}

// IsContainerStale is true if not explicitly stated in TestData for the mock client
func (client MockClient) IsContainerStale(cont t.Container) (bool, t.ImageID, error) {
func (client MockClient) IsContainerStale(cont t.Container, params t.UpdateParams) (bool, t.ImageID, error) {
stale, found := client.TestData.Staleness[cont.Name()]
if !found {
stale = true
Expand Down
14 changes: 6 additions & 8 deletions internal/actions/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ func Update(client container.Client, params types.UpdateParams) (types.Report, e
staleCheckFailed := 0

for i, targetContainer := range containers {
stale, newestImage, err := client.IsContainerStale(targetContainer)
shouldUpdate := stale && !params.NoRestart && !params.MonitorOnly && !targetContainer.IsMonitorOnly()
stale, newestImage, err := client.IsContainerStale(targetContainer, params)
shouldUpdate := stale && !params.NoRestart && !targetContainer.IsMonitorOnly(params)
if err == nil && shouldUpdate {
// Check to make sure we have all the necessary information for recreating the container
err = targetContainer.VerifyConfiguration()
Expand Down Expand Up @@ -72,12 +72,10 @@ func Update(client container.Client, params types.UpdateParams) (types.Report, e
UpdateImplicitRestart(containers)

var containersToUpdate []types.Container
if !params.MonitorOnly {
for _, c := range containers {
if !c.IsMonitorOnly() {
containersToUpdate = append(containersToUpdate, c)
progress.MarkForUpdate(c.ID())
}
for _, c := range containers {
if !c.IsMonitorOnly(params) {
containersToUpdate = append(containersToUpdate, c)
progress.MarkForUpdate(c.ID())
}
}

Expand Down
76 changes: 74 additions & 2 deletions internal/actions/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,84 @@ var _ = Describe("the update action", func() {
false,
false,
)
_, err := actions.Update(client, types.UpdateParams{MonitorOnly: true})
_, err := actions.Update(client, types.UpdateParams{Cleanup: true, MonitorOnly: true})
Expect(err).NotTo(HaveOccurred())
Expect(client.TestData.TriedToRemoveImageCount).To(Equal(0))
})
})
When("watchtower has been instructed to have label take precedence", func() {
It("it should update containers when monitor only is set to false", func() {
client := CreateMockClient(
&TestData{
//NameOfContainerToKeep: "test-container-02",
Containers: []types.Container{
CreateMockContainerWithConfig(
"test-container-02",
"test-container-02",
"fake-image2:latest",
false,
false,
time.Now(),
&dockerContainer.Config{
Labels: map[string]string{
"com.centurylinklabs.watchtower.monitor-only": "false",
},
}),
},
},
false,
false,
)
_, err := actions.Update(client, types.UpdateParams{Cleanup: true, MonitorOnly: true, LabelPrecedence: true})
Expect(err).NotTo(HaveOccurred())
Expect(client.TestData.TriedToRemoveImageCount).To(Equal(1))
})
It("it should update not containers when monitor only is set to true", func() {
client := CreateMockClient(
&TestData{
//NameOfContainerToKeep: "test-container-02",
Containers: []types.Container{
CreateMockContainerWithConfig(
"test-container-02",
"test-container-02",
"fake-image2:latest",
false,
false,
time.Now(),
&dockerContainer.Config{
Labels: map[string]string{
"com.centurylinklabs.watchtower.monitor-only": "true",
},
}),
},
},
false,
false,
)
_, err := actions.Update(client, types.UpdateParams{Cleanup: true, MonitorOnly: true, LabelPrecedence: true})
Expect(err).NotTo(HaveOccurred())
Expect(client.TestData.TriedToRemoveImageCount).To(Equal(0))
})
It("it should update not containers when monitor only is not set", func() {
client := CreateMockClient(
&TestData{
Containers: []types.Container{
CreateMockContainer(
"test-container-01",
"test-container-01",
"fake-image:latest",
time.Now()),
},
},
false,
false,
)
_, err := actions.Update(client, types.UpdateParams{Cleanup: true, MonitorOnly: true, LabelPrecedence: true})
Expect(err).NotTo(HaveOccurred())
Expect(client.TestData.TriedToRemoveImageCount).To(Equal(0))
})

})
})
})

When("watchtower has been instructed to run lifecycle hooks", func() {
Expand Down
6 changes: 6 additions & 0 deletions internal/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
"log-level",
viper.GetString("WATCHTOWER_LOG_LEVEL"),
"The maximum log level that will be written to STDERR. Possible values: panic, fatal, error, warn, info, debug or trace")

flags.BoolP(
"label-take-precedence",
"",
viper.GetBool("WATCHTOWER_LABEL_TAKE_PRECEDENCE"),
"Label applied to containers take precedence over arguments")
}

// RegisterNotificationFlags that are used by watchtower to send notifications
Expand Down
Binary file added oryxBuildBinary
Binary file not shown.
6 changes: 3 additions & 3 deletions pkg/container/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type Client interface {
StopContainer(t.Container, time.Duration) error
StartContainer(t.Container) (t.ContainerID, error)
RenameContainer(t.Container, string) error
IsContainerStale(t.Container) (stale bool, latestImage t.ImageID, err error)
IsContainerStale(t.Container, t.UpdateParams) (stale bool, latestImage t.ImageID, err error)
ExecuteCommand(containerID t.ContainerID, command string, timeout int) (SkipUpdate bool, err error)
RemoveImageByID(t.ImageID) error
WarnOnHeadPullFailed(container t.Container) bool
Expand Down Expand Up @@ -308,10 +308,10 @@ func (client dockerClient) RenameContainer(c t.Container, newName string) error
return client.api.ContainerRename(bg, string(c.ID()), newName)
}

func (client dockerClient) IsContainerStale(container t.Container) (stale bool, latestImage t.ImageID, err error) {
func (client dockerClient) IsContainerStale(container t.Container, params t.UpdateParams) (stale bool, latestImage t.ImageID, err error) {
ctx := context.Background()

if !client.PullImages || container.IsNoPull() {
if container.IsNoPull(params) {
log.Debugf("Skipping image pull.")
} else if err := client.PullImage(ctx, container); err != nil {
return false, container.SafeImageID(), err
Expand Down
49 changes: 23 additions & 26 deletions pkg/container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
package container

import (
"errors"
"fmt"
"strconv"
"strings"

"github.com/containrrr/watchtower/internal/util"
wt "github.com/containrrr/watchtower/pkg/types"
"github.com/sirupsen/logrus"

"github.com/docker/docker/api/types"
dockercontainer "github.com/docker/docker/api/types/container"
Expand Down Expand Up @@ -129,36 +131,31 @@ 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
// IsMonitorOnly returns whether the container should only be monitored based on values of
// the monitor-only label, the monitor-only argument and the label-take-precedence argument.
func (c Container) IsMonitorOnly(params wt.UpdateParams) bool {
return c.getContainerOrGlobalBool(params.MonitorOnly, monitorOnlyLabel, params.LabelPrecedence)
}

// IsNoPull returns the value of the no-pull label. If the label is not set
// then false is returned.
func (c Container) IsNoPull() bool {
rawBool, ok := c.getLabelValue(noPullLabel)
if !ok {
return false
}
// IsNoPull returns whether the image should be pulled based on values of
// the no-pull label, the no-pull argument and the label-take-precedence argument.
func (c Container) IsNoPull(params wt.UpdateParams) bool {
return c.getContainerOrGlobalBool(params.NoPull, noPullLabel, params.LabelPrecedence)
}

parsedBool, err := strconv.ParseBool(rawBool)
if err != nil {
return false
func (c Container) getContainerOrGlobalBool(globalVal bool, label string, contPrecedence bool) bool {
if contVal, err := c.getBoolLabelValue(label); err != nil {
if !errors.Is(err, errorLabelNotFound) {
logrus.WithField("error", err).WithField("label", label).Warn("Failed to parse label value")
}
return globalVal
} else {
if contPrecedence {
return contVal
} else {
return contVal || globalVal
}
}

return parsedBool
}

// Scope returns the value of the scope UID label and if the label
Expand Down
Loading