Skip to content

Commit

Permalink
feat: add a label take precedence argument (#1754)
Browse files Browse the repository at this point in the history
Co-authored-by: nils måsén <[email protected]>
  • Loading branch information
jebabin and piksel authored Sep 16, 2023
1 parent 1d5a8d9 commit 650acde
Show file tree
Hide file tree
Showing 14 changed files with 236 additions and 93 deletions.
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",
envString("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

0 comments on commit 650acde

Please sign in to comment.