Adding new runtime param delay-days that is used to control whether container is updated to new image immediately or wait until the indicated days have passed

This commit is contained in:
Peter Wilhelm 2023-12-17 19:58:33 -06:00
parent 0a14f3aa9c
commit a8279b47fe
5 changed files with 59 additions and 4 deletions

View file

@ -39,6 +39,7 @@ var (
disableContainers []string
notifier t.Notifier
timeout time.Duration
delayDays int
lifecycleHooks bool
rollingRestart bool
scope string
@ -96,6 +97,7 @@ func PreRun(cmd *cobra.Command, _ []string) {
enableLabel, _ = f.GetBool("label-enable")
disableContainers, _ = f.GetStringSlice("disable-containers")
delayDays, _ = f.GetInt("delay-days")
lifecycleHooks, _ = f.GetBool("enable-lifecycle-hooks")
rollingRestart, _ = f.GetBool("rolling-restart")
scope, _ = f.GetString("scope")
@ -364,6 +366,7 @@ func runUpdatesWithNotifications(filter t.Filter) *metrics.Metric {
NoRestart: noRestart,
Timeout: timeout,
MonitorOnly: monitorOnly,
DelayDays: delayDays,
LifecycleHooks: lifecycleHooks,
RollingRestart: rollingRestart,
LabelPrecedence: labelPrecedence,

View file

@ -381,6 +381,16 @@ Environment Variable: WATCHTOWER_HTTP_API_METRICS
Default: false
```
## Delayed Update
Only update container to latest version of image if some number of days have passed since it has been published. This option may be useful for those who wish to avoid updating prior to the new version having some time in the field prior to updating in case there are critical defects found and released in a subsequent version.
```text
Argument: --delay-days
Environment Variable: WATCHTOWER_DELAY_DAYS
Type: Integer
Default: false
```
## Scheduling
[Cron expression](https://pkg.go.dev/github.com/robfig/cron@v1.2.0?tab=doc#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression
can be defined, but not both. An example: `--schedule "0 0 4 * * *"`

View file

@ -147,6 +147,12 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
envBool("WATCHTOWER_LIFECYCLE_HOOKS"),
"Enable the execution of commands triggered by pre- and post-update lifecycle hooks")
flags.IntP(
"delay-days",
"0",
envInt("WATCHTOWER_DELAY_DAYS"),
"Number of days to wait for new image version to be in place prior to installing it")
flags.BoolP(
"rolling-restart",
"",

View file

@ -325,10 +325,25 @@ func (client dockerClient) IsContainerStale(container t.Container, params t.Upda
return false, container.SafeImageID(), err
}
return client.HasNewImage(ctx, container)
return client.HasNewImage(ctx, container, params)
}
func (client dockerClient) HasNewImage(ctx context.Context, container t.Container) (hasNew bool, latestImage t.ImageID, err error) {
// Date strings sometimes vary in how many digits after the decimal point are present. This function
// standardizes them by removing the milliseconds.
func truncateMilliseconds(dateString string) string {
// Find the position of the dot (.) in the date string
dotIndex := strings.Index(dateString, ".")
// If the dot is found, truncate the string before the dot
if dotIndex != -1 {
return dateString[:dotIndex] + "Z"
}
// If the dot is not found, return the original string
return dateString
}
func (client dockerClient) HasNewImage(ctx context.Context, container t.Container, params t.UpdateParams) (hasNew bool, latestImage t.ImageID, err error) {
currentImageID := t.ImageID(container.ContainerInfo().ContainerJSONBase.Image)
imageName := container.ImageName()
@ -339,10 +354,30 @@ func (client dockerClient) HasNewImage(ctx context.Context, container t.Containe
newImageID := t.ImageID(newImageInfo.ID)
if newImageID == currentImageID {
log.Debugf("No new images found for %s", container.Name())
log.Debugf("No new images found for %s [ imageID %s ]", container.Name(), newImageID.ShortID())
return false, currentImageID, nil
}
// Disabled by default
if params.DelayDays > 0 {
// Define the layout string for the date format without milliseconds
layout := "2006-01-02T15:04:05Z"
newImageDate, error := time.Parse(layout, truncateMilliseconds(newImageInfo.Created))
if error != nil {
log.Errorf("Error parsing Created date (%s) for container %s latest label. Error: %s", newImageInfo.Created, container.Name(), error)
return false, currentImageID, nil
} else {
requiredDays := params.DelayDays
diffDays := int(time.Since(newImageDate).Hours() / 24)
if diffDays < requiredDays {
log.Infof("New image found for %s that is %d days since publication but update delayed until %d days", container.Name(), diffDays, requiredDays)
return false, currentImageID, nil
}
}
}
log.Infof("Found new %s image (%s)", imageName, newImageID.ShortID())
return true, newImageID, nil
}

View file

@ -12,6 +12,7 @@ type UpdateParams struct {
Timeout time.Duration
MonitorOnly bool
NoPull bool
DelayDays int
LifecycleHooks bool
RollingRestart bool
LabelPrecedence bool