From 14b9f9ad7e6718b8ff5056bd3c4e501ab9c18317 Mon Sep 17 00:00:00 2001 From: Parker Van Roy Date: Fri, 16 Apr 2021 15:29:56 -0400 Subject: Autoupdate local label functional Digests were used to compare local image and container image Registry alias added for Image Policy Refactored to integrate new feature + change some naming conventions Tested this using a modified version of the docs autoupdate instructions & it worked successfully Signed-off-by: Parker Van Roy --- go.sum | 1 + pkg/autoupdate/autoupdate.go | 143 +++++++++++++++++++++++++++++++------------ 2 files changed, 106 insertions(+), 38 deletions(-) diff --git a/go.sum b/go.sum index 0de830099..f3940d4f6 100644 --- a/go.sum +++ b/go.sum @@ -209,6 +209,7 @@ github.com/containers/ocicrypt v1.0.3/go.mod h1:CUBa+8MRNL/VkpxYIpaMtgn1WgXGyvPQ github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= github.com/containers/ocicrypt v1.1.1 h1:prL8l9w3ntVqXvNH1CiNn5ENjcCnr38JqpSyvKKB4GI= github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/containers/podman v1.9.3 h1:GLQceCWhFkeTTYSU2qrpS3rsNYrrsilkUMF9LwNp4lg= github.com/containers/psgo v1.5.2 h1:3aoozst/GIwsrr/5jnFy3FrJay98uujPCu9lTuSZ/Cw= github.com/containers/psgo v1.5.2/go.mod h1:2ubh0SsreMZjSXW1Hif58JrEcFudQyIy9EzPUWfawVU= github.com/containers/storage v1.23.5/go.mod h1:ha26Q6ngehFNhf3AWoXldvAvwI4jFe3ETQAf/CeZPyM= diff --git a/pkg/autoupdate/autoupdate.go b/pkg/autoupdate/autoupdate.go index e271b9466..ad790079e 100644 --- a/pkg/autoupdate/autoupdate.go +++ b/pkg/autoupdate/autoupdate.go @@ -33,15 +33,25 @@ type Policy string const ( // PolicyDefault is the default policy denoting no auto updates. PolicyDefault Policy = "disabled" - // PolicyNewImage is the policy to update as soon as there's a new image found. - PolicyNewImage = "image" + // PolicyRegistryImage is the policy to update as soon as there's a new image found. + PolicyRegistryImage = "image" + // PolicyLocalImage is the policy to run auto-update based on a local image + PolicyLocalImage = "local" ) // Map for easy lookups of supported policies. var supportedPolicies = map[string]Policy{ "": PolicyDefault, "disabled": PolicyDefault, - "image": PolicyNewImage, + "image": PolicyRegistryImage, + "registry": PolicyRegistryImage, + "local": PolicyLocalImage, +} + +// Struct for tying a container to it's autoupdate policy +type PolicyContainer struct { + p Policy + ctr *libpod.Container } // LookupPolicy looks up the corresponding Policy for the specified @@ -99,11 +109,17 @@ func ValidateImageReference(imageName string) error { } // AutoUpdate looks up containers with a specified auto-update policy and acts -// accordingly. If the policy is set to PolicyNewImage, it checks if the image +// accordingly. +// +// If the policy is set to PolicyRegistryImage, it checks if the image // on the remote registry is different than the local one. If the image digests // differ, it pulls the remote image and restarts the systemd unit running the // container. // +// If the policy is set to PolicyLocalImage, it checks if the image +// of a running container is different than the local one. If the image digests +// differ, it restarts the systemd unit with the new image. +// // It returns a slice of successfully restarted systemd units and a slice of // errors encountered during auto update. func AutoUpdate(runtime *libpod.Runtime, options Options) ([]string, []error) { @@ -134,7 +150,7 @@ func AutoUpdate(runtime *libpod.Runtime, options Options) ([]string, []error) { // Update images. containersToRestart := []*libpod.Container{} updatedRawImages := make(map[string]bool) - for imageID, containers := range containerMap { + for imageID, policyContainers := range containerMap { image, exists := imageMap[imageID] if !exists { errs = append(errs, errors.Errorf("container image ID %q not found in local storage", imageID)) @@ -143,34 +159,53 @@ func AutoUpdate(runtime *libpod.Runtime, options Options) ([]string, []error) { // Now we have to check if the image of any containers must be updated. // Note that the image ID is NOT enough for this check as a given image // may have multiple tags. - for i, ctr := range containers { - rawImageName := ctr.RawImageName() + for i, pc := range policyContainers { + cid := pc.ctr.ID() + rawImageName := pc.ctr.RawImageName() if rawImageName == "" { - errs = append(errs, errors.Errorf("error auto-updating container %q: raw-image name is empty", ctr.ID())) - } - labels := ctr.Labels() - authFilePath, exists := labels[AuthfileLabel] - if exists { - options.Authfile = authFilePath + errs = append(errs, errors.Errorf("error auto-updating container %q: raw-image name is empty", pc.ctr.ID())) } - needsUpdate, err := newerImageAvailable(runtime, image, rawImageName, options) - if err != nil { - errs = append(errs, errors.Wrapf(err, "error auto-updating container %q: image check for %q failed", ctr.ID(), rawImageName)) - continue - } - if !needsUpdate { - continue - } - logrus.Infof("Auto-updating container %q using image %q", ctr.ID(), rawImageName) - if _, updated := updatedRawImages[rawImageName]; !updated { - _, err = updateImage(runtime, rawImageName, options) + + switch pc.p { + // Sanity Check, should be unreachable code + case PolicyDefault: + errs = append(errs, errors.Errorf("error auto-updating container %q: invalid policy", cid)) + + // Registry Autoupdate Containers pull new images and are flagged for restart. + case PolicyRegistryImage: + readAuthenticationPath(pc.ctr, options) + needsUpdate, err := newerRemoteImageAvailable(runtime, image, rawImageName, options) if err != nil { - errs = append(errs, errors.Wrapf(err, "error auto-updating container %q: image update for %q failed", ctr.ID(), rawImageName)) + errs = append(errs, errors.Wrapf(err, "error auto-updating container %q: image check for %q failed", cid, rawImageName)) continue } - updatedRawImages[rawImageName] = true + + if needsUpdate { + logrus.Infof("Auto-updating container %q using registry image %q", cid, rawImageName) + if _, updated := updatedRawImages[rawImageName]; !updated { + _, err = updateImage(runtime, rawImageName, options) + if err != nil { + errs = append(errs, errors.Wrapf(err, "error auto-updating container %q: image update for %q failed", cid, rawImageName)) + continue + } + updatedRawImages[rawImageName] = true + } + containersToRestart = append(containersToRestart, policyContainers[i].ctr) + } + // Local Autoupdate Containers with an update are flagged for restart. + case PolicyLocalImage: + // This avoids restarting containers unnecessarily. + needsUpdate, err := newerLocalImageAvailable(image, rawImageName) + if err != nil { + errs = append(errs, errors.Wrapf(err, "error auto-updating container %q: image check for %q failed", cid, rawImageName)) + continue + } + + if needsUpdate { + logrus.Infof("Auto-updating container %q using local image %q", cid, rawImageName) + containersToRestart = append(containersToRestart, policyContainers[i].ctr) + } } - containersToRestart = append(containersToRestart, containers[i]) } } @@ -198,14 +233,14 @@ func AutoUpdate(runtime *libpod.Runtime, options Options) ([]string, []error) { // imageContainersMap generates a map[image ID] -> [containers using the image] // of all containers with a valid auto-update policy. -func imageContainersMap(runtime *libpod.Runtime) (map[string][]*libpod.Container, []error) { +func imageContainersMap(runtime *libpod.Runtime) (map[string][]PolicyContainer, []error) { allContainers, err := runtime.GetAllContainers() if err != nil { return nil, []error{err} } errors := []error{} - imageMap := make(map[string][]*libpod.Container) + containerMap := make(map[string][]PolicyContainer) for i, ctr := range allContainers { state, err := ctr.State() if err != nil { @@ -230,22 +265,35 @@ func imageContainersMap(runtime *libpod.Runtime) (map[string][]*libpod.Container continue } - // Skip non-image labels (could be explicitly disabled). - if policy != PolicyNewImage { + // Skip labels not related to autoupdate + if policy != PolicyDefault { + id, _ := ctr.Image() + pc := PolicyContainer{ + p: policy, + ctr: allContainers[i], + } + containerMap[id] = append(containerMap[id], pc) + // Now we know that `ctr` is configured for auto updates. + } else { continue } - - // Now we know that `ctr` is configured for auto updates. - id, _ := ctr.Image() - imageMap[id] = append(imageMap[id], allContainers[i]) } - return imageMap, errors + return containerMap, errors +} + +// readAuthenticationPath reads a container's labels and reads authentication path into options +func readAuthenticationPath(ctr *libpod.Container, options Options) { + labels := ctr.Labels() + authFilePath, exists := labels[AuthfileLabel] + if exists { + options.Authfile = authFilePath + } } -// newerImageAvailable returns true if there corresponding image on the remote +// newerRemoteImageAvailable returns true if there corresponding image on the remote // registry is newer. -func newerImageAvailable(runtime *libpod.Runtime, img *image.Image, origName string, options Options) (bool, error) { +func newerRemoteImageAvailable(runtime *libpod.Runtime, img *image.Image, origName string, options Options) (bool, error) { remoteRef, err := docker.ParseReference("//" + origName) if err != nil { return false, err @@ -282,6 +330,25 @@ func newerImageAvailable(runtime *libpod.Runtime, img *image.Image, origName str return img.Digest().String() != remoteDigest.String(), nil } +// newerLocalImageAvailable returns true if the container and local image have different digests +func newerLocalImageAvailable(img *image.Image, rawImageName string) (bool, error) { + rt, err := libpod.NewRuntime(context.TODO()) + if err != nil { + return false, err + } + + localImg, err := rt.ImageRuntime().NewFromLocal(rawImageName) + if err != nil { + return false, err + } + + localDigest := localImg.Digest().String() + + ctrDigest := img.Digest().String() + + return localDigest != ctrDigest, nil +} + // updateImage pulls the specified image. func updateImage(runtime *libpod.Runtime, name string, options Options) (*image.Image, error) { sys := runtime.SystemContext() -- cgit v1.2.3-54-g00ecf