diff options
Diffstat (limited to 'pkg/autoupdate')
-rw-r--r-- | pkg/autoupdate/autoupdate.go | 175 |
1 files changed, 109 insertions, 66 deletions
diff --git a/pkg/autoupdate/autoupdate.go b/pkg/autoupdate/autoupdate.go index e271b9466..0a13e7e74 100644 --- a/pkg/autoupdate/autoupdate.go +++ b/pkg/autoupdate/autoupdate.go @@ -5,16 +5,16 @@ import ( "os" "sort" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/systemd" systemdDefine "github.com/containers/podman/v3/pkg/systemd/define" - "github.com/containers/podman/v3/pkg/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -33,17 +33,24 @@ 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 = "registry" + // 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, } +// policyMapper is used for tying a container to it's autoupdate policy +type policyMapper map[Policy][]*libpod.Container + // LookupPolicy looks up the corresponding Policy for the specified // string. If none is found, an errors is returned including the list of // supported policies. @@ -99,11 +106,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) { @@ -113,12 +126,15 @@ func AutoUpdate(runtime *libpod.Runtime, options Options) ([]string, []error) { return nil, errs } - // Create a map from `image ID -> *image.Image` for image lookups. - imagesSlice, err := runtime.ImageRuntime().GetImages() + // Create a map from `image ID -> *libimage.Image` for image lookups. + listOptions := &libimage.ListImagesOptions{ + Filters: []string{"readonly=false"}, + } + imagesSlice, err := runtime.LibimageRuntime().ListImages(context.Background(), nil, listOptions) if err != nil { return nil, []error{err} } - imageMap := make(map[string]*image.Image) + imageMap := make(map[string]*libimage.Image) for i := range imagesSlice { imageMap[imagesSlice[i].ID()] = imagesSlice[i] } @@ -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, policyMapper := 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,50 @@ 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 _, registryCtr := range policyMapper[PolicyRegistryImage] { + cid := registryCtr.ID() + rawImageName := registryCtr.RawImageName() if rawImageName == "" { - errs = append(errs, errors.Errorf("error auto-updating container %q: raw-image name is empty", ctr.ID())) + errs = append(errs, errors.Errorf("error registry auto-updating container %q: raw-image name is empty", cid)) } - labels := ctr.Labels() - authFilePath, exists := labels[AuthfileLabel] - if exists { - options.Authfile = authFilePath - } - needsUpdate, err := newerImageAvailable(runtime, image, rawImageName, options) + readAuthenticationPath(registryCtr, options) + needsUpdate, err := newerRemoteImageAvailable(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)) + errs = append(errs, errors.Wrapf(err, "error registry auto-updating container %q: image check for %q failed", cid, rawImageName)) continue } - if !needsUpdate { + + 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 registry auto-updating container %q: image update for %q failed", cid, rawImageName)) + continue + } + updatedRawImages[rawImageName] = true + } + containersToRestart = append(containersToRestart, registryCtr) + } + } + + for _, localCtr := range policyMapper[PolicyLocalImage] { + cid := localCtr.ID() + rawImageName := localCtr.RawImageName() + if rawImageName == "" { + errs = append(errs, errors.Errorf("error locally auto-updating container %q: raw-image name is empty", cid)) + } + // This avoids restarting containers unnecessarily. + needsUpdate, err := newerLocalImageAvailable(runtime, image, rawImageName) + if err != nil { + errs = append(errs, errors.Wrapf(err, "error locally auto-updating container %q: image check for %q failed", cid, rawImageName)) continue } - logrus.Infof("Auto-updating container %q using image %q", ctr.ID(), 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", ctr.ID(), rawImageName)) - continue - } - updatedRawImages[rawImageName] = true + + if needsUpdate { + logrus.Infof("Auto-updating container %q using local image %q", cid, rawImageName) + containersToRestart = append(containersToRestart, localCtr) } - containersToRestart = append(containersToRestart, containers[i]) } } @@ -198,15 +230,15 @@ 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]policyMapper, []error) { allContainers, err := runtime.GetAllContainers() if err != nil { return nil, []error{err} } errors := []error{} - imageMap := make(map[string][]*libpod.Container) - for i, ctr := range allContainers { + containerMap := make(map[string]policyMapper) + for _, ctr := range allContainers { state, err := ctr.State() if err != nil { errors = append(errors, err) @@ -230,28 +262,42 @@ 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 { continue + } else { + id, _ := ctr.Image() + policyMap, exists := containerMap[id] + if !exists { + policyMap = make(map[Policy][]*libpod.Container) + } + policyMap[policy] = append(policyMap[policy], ctr) + containerMap[id] = policyMap + // Now we know that `ctr` is configured for auto updates. } - - // 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 *libimage.Image, origName string, options Options) (bool, error) { remoteRef, err := docker.ParseReference("//" + origName) if err != nil { return false, err } - data, err := img.Inspect(context.Background()) + data, err := img.Inspect(context.Background(), false) if err != nil { return false, err } @@ -282,32 +328,29 @@ func newerImageAvailable(runtime *libpod.Runtime, img *image.Image, origName str return img.Digest().String() != remoteDigest.String(), nil } -// updateImage pulls the specified image. -func updateImage(runtime *libpod.Runtime, name string, options Options) (*image.Image, error) { - sys := runtime.SystemContext() - registryOpts := image.DockerRegistryOptions{} - signaturePolicyPath := "" - - if sys != nil { - registryOpts.OSChoice = sys.OSChoice - registryOpts.ArchitectureChoice = sys.OSChoice - registryOpts.DockerCertPath = sys.DockerCertPath - signaturePolicyPath = sys.SignaturePolicyPath +// newerLocalImageAvailable returns true if the container and local image have different digests +func newerLocalImageAvailable(runtime *libpod.Runtime, img *libimage.Image, rawImageName string) (bool, error) { + localImg, _, err := runtime.LibimageRuntime().LookupImage(rawImageName, nil) + if err != nil { + return false, err } - newImage, err := runtime.ImageRuntime().New(context.Background(), - docker.Transport.Name()+"://"+name, - signaturePolicyPath, - options.Authfile, - os.Stderr, - ®istryOpts, - image.SigningOptions{}, - nil, - util.PullImageAlways, - nil, - ) + 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) (*libimage.Image, error) { + pullOptions := &libimage.PullOptions{} + pullOptions.AuthFilePath = options.Authfile + pullOptions.Writer = os.Stderr + + pulledImages, err := runtime.LibimageRuntime().Pull(context.Background(), name, config.PullPolicyAlways, pullOptions) if err != nil { return nil, err } - return newImage, nil + return pulledImages[0], nil } |