From 8bcf748eb04760570269c8b65e28dc26b7233aef Mon Sep 17 00:00:00 2001 From: Peter Wilhelm Date: Thu, 21 Dec 2023 19:28:25 -0500 Subject: [PATCH 1/2] Adjusting implementation of delay-days to those suggested in PR review comments, to perform necessary checks in update.go. Added some comments where useful for understanding existing functionality. --- internal/actions/update.go | 49 +++++++++++++++++++++++++++++++++++--- pkg/container/client.go | 35 --------------------------- 2 files changed, 46 insertions(+), 38 deletions(-) diff --git a/internal/actions/update.go b/internal/actions/update.go index 8853c6e..1754846 100644 --- a/internal/actions/update.go +++ b/internal/actions/update.go @@ -2,6 +2,8 @@ package actions import ( "errors" + "strings" + "time" "github.com/containrrr/watchtower/internal/util" "github.com/containrrr/watchtower/pkg/container" @@ -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()) @@ -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) } @@ -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 over containers with stale set to false (unless LinkedToRestarting set) var containersToUpdate []types.Container for _, c := range containers { if !c.IsMonitorOnly(params) { @@ -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, but since these are not always in ISO 8601 with 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 + +} diff --git a/pkg/container/client.go b/pkg/container/client.go index 18118d0..c249f2e 100644 --- a/pkg/container/client.go +++ b/pkg/container/client.go @@ -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() @@ -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 } From bb9833e16c585f0c59aedf4ba10b721a35927780 Mon Sep 17 00:00:00 2001 From: Peter Wilhelm Date: Thu, 21 Dec 2023 19:35:35 -0500 Subject: [PATCH 2/2] Comment rephrasing to improve clarify/format --- internal/actions/update.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/actions/update.go b/internal/actions/update.go index 1754846..63196ce 100644 --- a/internal/actions/update.go +++ b/internal/actions/update.go @@ -89,7 +89,7 @@ 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 over containers with stale set to false (unless LinkedToRestarting set) + // 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) { @@ -286,7 +286,7 @@ func linkedContainerMarkedForRestart(links []string, containers []types.Containe } // Finds the difference between now and a given date, in full days. Input date is expected to originate -// from an image's Created attribute, but since these are not always in ISO 8601 with the same number of +// 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) {