Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjusting implementation of delay-days to those suggested in PR revie… #1

Merged
merged 2 commits into from
Dec 22, 2023
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
49 changes: 46 additions & 3 deletions internal/actions/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package actions

import (
"errors"
"strings"
"time"

"github.com/containrrr/watchtower/internal/util"
"github.com/containrrr/watchtower/pkg/container"
Expand Down Expand Up @@ -33,13 +35,23 @@ func Update(client container.Client, params types.UpdateParams) (types.Report, e
staleCheckFailed := 0

for i, targetContainer := range containers {
// stale will be true if there is a more recent image than the current container is using
stale, newestImage, err := client.IsContainerStale(targetContainer, params)
shouldUpdate := stale && !params.NoRestart && !targetContainer.IsMonitorOnly(params)
imageUpdateDelayResolved := true
imageAgeDays := 0
if err == nil && shouldUpdate {
// Check to make sure we have all the necessary information for recreating the container
// Check to make sure we have all the necessary information for recreating the container, including ImageInfo
err = targetContainer.VerifyConfiguration()
// If the image information is incomplete and trace logging is enabled, log it for further diagnosis
if err != nil && log.IsLevelEnabled(log.TraceLevel) {
if err == nil {
if params.DelayDays > 0 {
imageAgeDays, err := getImageAgeDays(targetContainer.ImageInfo().Created)
if err == nil {
imageUpdateDelayResolved = imageAgeDays >= params.DelayDays
}
}
} else if log.IsLevelEnabled(log.TraceLevel) {
// If the image information is incomplete and trace logging is enabled, log it for further diagnosis
imageInfo := targetContainer.ImageInfo()
log.Tracef("Image info: %#v", imageInfo)
log.Tracef("Container info: %#v", targetContainer.ContainerInfo())
Expand All @@ -54,6 +66,11 @@ func Update(client container.Client, params types.UpdateParams) (types.Report, e
stale = false
staleCheckFailed++
progress.AddSkipped(targetContainer, err)
} else if !imageUpdateDelayResolved {
log.Infof("New image found for %s that was created %d day(s) ago but update delayed until %d day(s) after creation", targetContainer.Name(), imageAgeDays, params.DelayDays)
// technically the container is stale but we set it to false here because it is this stale flag that tells downstream methods whether to perform the update
stale = false
progress.AddScanned(targetContainer, newestImage)
} else {
progress.AddScanned(targetContainer, newestImage)
}
Expand All @@ -71,6 +88,8 @@ func Update(client container.Client, params types.UpdateParams) (types.Report, e

UpdateImplicitRestart(containers)

// containersToUpdate will contain all containers, not just those that need to be updated. The "stale" flag is checked via container.ToRestart()
// within stopContainersInReversedOrder and restartContainersInSortedOrder to skip containers with stale set to false (unless LinkedToRestarting set)
var containersToUpdate []types.Container
for _, c := range containers {
if !c.IsMonitorOnly(params) {
Expand Down Expand Up @@ -265,3 +284,27 @@ func linkedContainerMarkedForRestart(links []string, containers []types.Containe
}
return ""
}

// Finds the difference between now and a given date, in full days. Input date is expected to originate
// from an image's Created attribute in ISO 8601, but since these do not always contain the same number of
// digits for milliseconds, the function also accounts for variations.
func getImageAgeDays(imageCreatedDateTime string) (int, error) {

// Date strings sometimes vary in how many digits after the decimal point are present. If present, drop millisecond portion to standardize.
dotIndex := strings.Index(imageCreatedDateTime, ".")
if dotIndex != -1 {
imageCreatedDateTime = imageCreatedDateTime[:dotIndex] + "Z"
}

// Define the layout string for the date format without milliseconds
layout := "2006-01-02T15:04:05Z"
imageCreatedDate, error := time.Parse(layout, imageCreatedDateTime)

if error != nil {
log.Errorf("Error parsing imageCreatedDateTime date (%s). Error: %s", imageCreatedDateTime, error)
return -1, error
}

return int(time.Since(imageCreatedDate).Hours() / 24), nil

}
35 changes: 0 additions & 35 deletions pkg/container/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,21 +328,6 @@ func (client dockerClient) IsContainerStale(container t.Container, params t.Upda
return client.HasNewImage(ctx, container, params)
}

// 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()
Expand All @@ -358,26 +343,6 @@ func (client dockerClient) HasNewImage(ctx context.Context, container t.Containe
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 was created %d day(s) ago but update delayed until %d day(s) after creation", container.Name(), diffDays, requiredDays)
return false, currentImageID, nil
}
}
}

log.Infof("Found new %s image (%s)", imageName, newImageID.ShortID())
return true, newImageID, nil
}
Expand Down