From 0f7d54b0260c1be992ee3b9cee359ef3a9e8bd21 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Thu, 22 Apr 2021 08:01:12 +0200 Subject: migrate Podman to containers/common/libimage Migrate the Podman code base over to `common/libimage` which replaces `libpod/image` and a lot of glue code entirely. Note that I tried to leave bread crumbs for changed tests. Miscellaneous changes: * Some errors yield different messages which required to alter some tests. * I fixed some pre-existing issues in the code. Others were marked as `//TODO`s to prevent the PR from exploding. * The `NamesHistory` of an image is returned as is from the storage. Previously, we did some filtering which I think is undesirable. Instead we should return the data as stored in the storage. * Touched handlers use the ABI interfaces where possible. * Local image resolution: previously Podman would match "foo" on "myfoo". This behaviour has been changed and Podman will now only match on repository boundaries such that "foo" would match "my/foo" but not "myfoo". I consider the old behaviour to be a bug, at the very least an exotic corner case. * Futhermore, "foo:none" does *not* resolve to a local image "foo" without tag anymore. It's a hill I am (almost) willing to die on. * `image prune` prints the IDs of pruned images. Previously, in some cases, the names were printed instead. The API clearly states ID, so we should stick to it. * Compat endpoint image removal with _force_ deletes the entire not only the specified tag. Signed-off-by: Valentin Rothberg --- pkg/domain/entities/manifest.go | 1 + pkg/domain/infra/abi/containers.go | 16 +- pkg/domain/infra/abi/containers_runlabel.go | 86 ++-- pkg/domain/infra/abi/images.go | 582 ++++++++++++---------------- pkg/domain/infra/abi/images_list.go | 61 +-- pkg/domain/infra/abi/manifest.go | 327 +++++++++------- pkg/domain/infra/abi/play.go | 59 ++- pkg/domain/infra/abi/system.go | 21 +- pkg/domain/infra/tunnel/images.go | 4 +- 9 files changed, 553 insertions(+), 604 deletions(-) (limited to 'pkg/domain') diff --git a/pkg/domain/entities/manifest.go b/pkg/domain/entities/manifest.go index 6a645e20b..3f89e4d30 100644 --- a/pkg/domain/entities/manifest.go +++ b/pkg/domain/entities/manifest.go @@ -8,6 +8,7 @@ type ManifestCreateOptions struct { All bool `schema:"all"` } +// swagger:model ManifestAddOpts type ManifestAddOptions struct { All bool `json:"all" schema:"all"` Annotation []string `json:"annotation" schema:"annotation"` diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 82f2a2424..ef3ccab0c 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -15,7 +15,6 @@ import ( "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/events" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/libpod/logs" "github.com/containers/podman/v3/pkg/cgroups" "github.com/containers/podman/v3/pkg/checkpoint" @@ -23,6 +22,7 @@ import ( "github.com/containers/podman/v3/pkg/domain/entities/reports" dfilters "github.com/containers/podman/v3/pkg/domain/filters" "github.com/containers/podman/v3/pkg/domain/infra/abi/terminal" + "github.com/containers/podman/v3/pkg/errorhandling" parallelctr "github.com/containers/podman/v3/pkg/parallel/ctr" "github.com/containers/podman/v3/pkg/ps" "github.com/containers/podman/v3/pkg/rootless" @@ -438,7 +438,8 @@ func (ic *ContainerEngine) ContainerCommit(ctx context.Context, nameOrID string, default: return nil, errors.Errorf("unrecognized image format %q", options.Format) } - sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) + + sc := ic.Libpod.SystemContext() coptions := buildah.CommitOptions{ SignaturePolicyPath: rtc.Engine.SignaturePolicyPath, ReportWriter: options.Writer, @@ -999,14 +1000,9 @@ func (ic *ContainerEngine) ContainerCleanup(ctx context.Context, namesOrIds []st if options.RemoveImage { _, imageName := ctr.Image() - ctrImage, err := ic.Libpod.ImageRuntime().NewFromLocal(imageName) - if err != nil { - report.RmiErr = err - reports = append(reports, &report) - continue - } - _, err = ic.Libpod.RemoveImage(ctx, ctrImage, false) - report.RmiErr = err + imageEngine := ImageEngine{Libpod: ic.Libpod} + _, rmErrors := imageEngine.Remove(ctx, []string{imageName}, entities.ImageRemoveOptions{}) + report.RmiErr = errorhandling.JoinErrors(rmErrors) } reports = append(reports, &report) diff --git a/pkg/domain/infra/abi/containers_runlabel.go b/pkg/domain/infra/abi/containers_runlabel.go index 2cabab988..199ae43ad 100644 --- a/pkg/domain/infra/abi/containers_runlabel.go +++ b/pkg/domain/infra/abi/containers_runlabel.go @@ -7,8 +7,9 @@ import ( "path/filepath" "strings" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/domain/entities" envLib "github.com/containers/podman/v3/pkg/env" "github.com/containers/podman/v3/utils" @@ -18,21 +19,48 @@ import ( ) func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, imageRef string, args []string, options entities.ContainerRunlabelOptions) error { - // First, get the image and pull it if needed. - img, err := ic.runlabelImage(ctx, label, imageRef, options) + pullOptions := &libimage.PullOptions{} + pullOptions.AuthFilePath = options.Authfile + pullOptions.CertDirPath = options.CertDir + pullOptions.Credentials = options.Credentials + pullOptions.SignaturePolicyPath = options.SignaturePolicy + pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify + + pullPolicy := config.PullPolicyNever + if options.Pull { + pullPolicy = config.PullPolicyMissing + } + if !options.Quiet { + pullOptions.Writer = os.Stderr + } + + pulledImages, err := ic.Libpod.LibimageRuntime().Pull(ctx, imageRef, pullPolicy, pullOptions) if err != nil { return err } + + if len(pulledImages) != 1 { + return errors.Errorf("internal error: expected an image to be pulled (or an error)") + } + // Extract the runlabel from the image. - runlabel, err := img.GetLabel(ctx, label) + labels, err := pulledImages[0].Labels(ctx) if err != nil { return err } + + var runlabel string + for k, v := range labels { + if strings.EqualFold(k, label) { + runlabel = v + break + } + } if runlabel == "" { return errors.Errorf("cannot find the value of label: %s in image: %s", label, imageRef) } - cmd, env, err := generateRunlabelCommand(runlabel, img, args, options) + cmd, env, err := generateRunlabelCommand(runlabel, pulledImages[0], imageRef, args, options) if err != nil { return err } @@ -76,36 +104,9 @@ func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, return utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...) } -// runlabelImage returns an image based on the specified image AND options. -func (ic *ContainerEngine) runlabelImage(ctx context.Context, label string, imageRef string, options entities.ContainerRunlabelOptions) (*image.Image, error) { - // First, look up the image locally. If we get an error and requested - // to pull, fallthrough and pull it. - img, err := ic.Libpod.ImageRuntime().NewFromLocal(imageRef) - switch { - case err == nil: - return img, nil - case !options.Pull: - return nil, err - default: - // Fallthrough and pull! - } - - pullOptions := entities.ImagePullOptions{ - Quiet: options.Quiet, - CertDir: options.CertDir, - SkipTLSVerify: options.SkipTLSVerify, - SignaturePolicy: options.SignaturePolicy, - Authfile: options.Authfile, - } - if _, err := pull(ctx, ic.Libpod.ImageRuntime(), imageRef, pullOptions, &label); err != nil { - return nil, err - } - return ic.Libpod.ImageRuntime().NewFromLocal(imageRef) -} - // generateRunlabelCommand generates the to-be-executed command as a string // slice along with a base environment. -func generateRunlabelCommand(runlabel string, img *image.Image, args []string, options entities.ContainerRunlabelOptions) ([]string, []string, error) { +func generateRunlabelCommand(runlabel string, img *libimage.Image, inputName string, args []string, options entities.ContainerRunlabelOptions) ([]string, []string, error) { var ( err error name, imageName string @@ -113,24 +114,25 @@ func generateRunlabelCommand(runlabel string, img *image.Image, args []string, o cmd []string ) - // TODO: How do we get global opts as done in v1? - // Extract the imageName (or ID). - imgNames := img.Names() + imgNames := img.NamesHistory() if len(imgNames) == 0 { imageName = img.ID() } else { + // The newest name is the first entry in the `NamesHistory` + // slice. imageName = imgNames[0] } // Use the user-specified name or extract one from the image. - if options.Name != "" { - name = options.Name - } else { - name, err = image.GetImageBaseName(imageName) - if err != nil { - return nil, nil, err + name = options.Name + if name == "" { + normalize := imageName + if !strings.HasPrefix(img.ID(), inputName) { + normalize = inputName } + splitImageName := strings.Split(normalize, "/") + name = splitImageName[len(splitImageName)-1] } // Append the user-specified arguments to the runlabel (command). diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 84c7ebecd..0364b00a3 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -3,15 +3,14 @@ package abi import ( "context" "fmt" - "io" "io/ioutil" "net/url" "os" "path" "path/filepath" "strconv" - "strings" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker" "github.com/containers/image/v5/docker/reference" @@ -19,14 +18,11 @@ import ( "github.com/containers/image/v5/signature" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" - "github.com/containers/image/v5/types" - "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/entities/reports" domainUtils "github.com/containers/podman/v3/pkg/domain/utils" + "github.com/containers/podman/v3/pkg/errorhandling" "github.com/containers/podman/v3/pkg/rootless" - "github.com/containers/podman/v3/pkg/util" "github.com/containers/storage" dockerRef "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" @@ -36,31 +32,84 @@ import ( ) func (ir *ImageEngine) Exists(_ context.Context, nameOrID string) (*entities.BoolReport, error) { - _, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) + exists, err := ir.Libpod.LibimageRuntime().Exists(nameOrID) if err != nil { - if errors.Cause(err) == define.ErrMultipleImages { - return &entities.BoolReport{Value: true}, nil - } - if errors.Cause(err) != define.ErrNoSuchImage { - return nil, err - } + return nil, err } - return &entities.BoolReport{Value: err == nil}, nil + return &entities.BoolReport{Value: exists}, nil } func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) ([]*reports.PruneReport, error) { - reports, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, opts.Filter) - if err != nil { - return nil, err + // NOTE: the terms "dangling" and "intermediate" are not used + // consistently across our code base. In libimage, "dangling" means + // that an image has no tags. "intermediate" means that an image is + // dangling and that no other image depends on it (i.e., has no + // children). + // + // While pruning usually refers to "dangling" images, it has always + // removed "intermediate" ones. + defaultOptions := &libimage.RemoveImagesOptions{ + Filters: append(opts.Filter, "intermediate=true", "containers=false", "readonly=false"), + WithSize: true, + } + + // `image prune --all` means to *also* remove images which are not in + // use by any container. Since image filters are chained, we need to + // do two look ups since the default ones are a subset of all. + unusedOptions := &libimage.RemoveImagesOptions{ + Filters: append(opts.Filter, "containers=false", "readonly=false"), + WithSize: true, + } + + var pruneReports []*reports.PruneReport + + // Now prune all images until we converge. + numPreviouslyRemovedImages := 1 + for { + removedDefault, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, nil, defaultOptions) + if rmErrors != nil { + return nil, errorhandling.JoinErrors(rmErrors) + } + removedUnused, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, nil, unusedOptions) + if rmErrors != nil { + return nil, errorhandling.JoinErrors(rmErrors) + } + + for _, rmReport := range append(removedDefault, removedUnused...) { + r := *rmReport + pruneReports = append(pruneReports, &reports.PruneReport{ + Id: r.ID, + Size: uint64(r.Size), + }) + } + + numRemovedImages := len(removedDefault) + len(removedUnused) + if numRemovedImages+numPreviouslyRemovedImages == 0 { + break + } + numPreviouslyRemovedImages = numRemovedImages } - return reports, err + + return pruneReports, nil +} + +func toDomainHistoryLayer(layer *libimage.ImageHistory) entities.ImageHistoryLayer { + l := entities.ImageHistoryLayer{} + l.ID = layer.ID + l.Created = *layer.Created + l.CreatedBy = layer.CreatedBy + copy(l.Tags, layer.Tags) + l.Size = layer.Size + l.Comment = layer.Comment + return l } func (ir *ImageEngine) History(ctx context.Context, nameOrID string, opts entities.ImageHistoryOptions) (*entities.ImageHistoryReport, error) { - image, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, &libimage.LookupImageOptions{IgnorePlatform: true}) if err != nil { return nil, err } + results, err := image.History(ctx) if err != nil { return nil, err @@ -70,17 +119,17 @@ func (ir *ImageEngine) History(ctx context.Context, nameOrID string, opts entiti Layers: make([]entities.ImageHistoryLayer, len(results)), } - for i, layer := range results { - history.Layers[i] = ToDomainHistoryLayer(layer) + for i := range results { + history.Layers[i] = toDomainHistoryLayer(&results[i]) } return &history, nil } func (ir *ImageEngine) Mount(ctx context.Context, nameOrIDs []string, opts entities.ImageMountOptions) ([]*entities.ImageMountReport, error) { - var ( - images []*image.Image - err error - ) + if opts.All && len(nameOrIDs) > 0 { + return nil, errors.Errorf("cannot mix --all with images") + } + if os.Geteuid() != 0 { if driver := ir.Libpod.StorageConfig().GraphDriverName; driver != "vfs" { // Do not allow to mount a graphdriver that is not vfs if we are creating the userns as part @@ -96,219 +145,129 @@ func (ir *ImageEngine) Mount(ctx context.Context, nameOrIDs []string, opts entit os.Exit(ret) } } + + listImagesOptions := &libimage.ListImagesOptions{} if opts.All { - allImages, err := ir.Libpod.ImageRuntime().GetImages() - if err != nil { - return nil, err - } - for _, img := range allImages { - if !img.IsReadOnly() { - images = append(images, img) - } - } - } else { - for _, i := range nameOrIDs { - img, err := ir.Libpod.ImageRuntime().NewFromLocal(i) - if err != nil { - return nil, err - } - images = append(images, img) - } + listImagesOptions.Filters = []string{"readonly=false"} } - reports := make([]*entities.ImageMountReport, 0, len(images)) - for _, img := range images { - report := entities.ImageMountReport{Id: img.ID()} - if img.IsReadOnly() { - report.Err = errors.Errorf("mounting readonly %s image not supported", img.ID()) - } else { - report.Path, report.Err = img.Mount([]string{}, "") - } - reports = append(reports, &report) - } - if len(reports) > 0 { - return reports, nil - } - - images, err = ir.Libpod.ImageRuntime().GetImages() + images, err := ir.Libpod.LibimageRuntime().ListImages(ctx, nameOrIDs, listImagesOptions) if err != nil { return nil, err } + + mountReports := []*entities.ImageMountReport{} + listMountsOnly := !opts.All && len(nameOrIDs) == 0 for _, i := range images { - mounted, path, err := i.Mounted() - if err != nil { - if errors.Cause(err) == storage.ErrLayerUnknown { - continue - } - return nil, err - } - if mounted { - tags, err := i.RepoTags() + // TODO: the .Err fields are not used. This pre-dates the + // libimage migration but should be addressed at some point. + // A quick glimpse at cmd/podman/image/mount.go suggests that + // the errors needed to be handled there as well. + var mountPoint string + var err error + if listMountsOnly { + // We're only looking for mounted images. + mountPoint, err = i.Mountpoint() if err != nil { return nil, err } - reports = append(reports, &entities.ImageMountReport{ - Id: i.ID(), - Name: string(i.Digest()), - Repositories: tags, - Path: path, - }) - } - } - return reports, nil -} - -func (ir *ImageEngine) Unmount(ctx context.Context, nameOrIDs []string, options entities.ImageUnmountOptions) ([]*entities.ImageUnmountReport, error) { - var images []*image.Image - - if options.All { - allImages, err := ir.Libpod.ImageRuntime().GetImages() - if err != nil { - return nil, err - } - for _, img := range allImages { - if !img.IsReadOnly() { - images = append(images, img) + // Not mounted, so skip. + if mountPoint == "" { + continue } - } - } else { - for _, i := range nameOrIDs { - img, err := ir.Libpod.ImageRuntime().NewFromLocal(i) + } else { + mountPoint, err = i.Mount(ctx, nil, "") if err != nil { return nil, err } - images = append(images, img) } - } - reports := []*entities.ImageUnmountReport{} - for _, img := range images { - report := entities.ImageUnmountReport{Id: img.ID()} - mounted, _, err := img.Mounted() + tags, err := i.RepoTags() if err != nil { - // Errors will be caught in Unmount call below - // Default assumption to mounted - mounted = true - } - if !mounted { - continue - } - if err := img.Unmount(options.Force); err != nil { - if options.All && errors.Cause(err) == storage.ErrLayerNotMounted { - logrus.Debugf("Error umounting image %s, storage.ErrLayerNotMounted", img.ID()) - continue - } - report.Err = errors.Wrapf(err, "error unmounting image %s", img.ID()) + return nil, err } - reports = append(reports, &report) + mountReports = append(mountReports, &entities.ImageMountReport{ + Id: i.ID(), + Name: string(i.Digest()), + Repositories: tags, + Path: mountPoint, + }) } - return reports, nil + return mountReports, nil } -func ToDomainHistoryLayer(layer *image.History) entities.ImageHistoryLayer { - l := entities.ImageHistoryLayer{} - l.ID = layer.ID - l.Created = *layer.Created - l.CreatedBy = layer.CreatedBy - copy(l.Tags, layer.Tags) - l.Size = layer.Size - l.Comment = layer.Comment - return l -} - -func pull(ctx context.Context, runtime *image.Runtime, rawImage string, options entities.ImagePullOptions, label *string) (*entities.ImagePullReport, error) { - var writer io.Writer - if !options.Quiet { - writer = os.Stderr - } - - dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name()) - imageRef, err := alltransports.ParseImageName(rawImage) - if err != nil { - imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, rawImage)) - if err != nil { - return nil, errors.Wrapf(err, "invalid image reference %q", rawImage) - } +func (ir *ImageEngine) Unmount(ctx context.Context, nameOrIDs []string, options entities.ImageUnmountOptions) ([]*entities.ImageUnmountReport, error) { + if options.All && len(nameOrIDs) > 0 { + return nil, errors.Errorf("cannot mix --all with images") } - var registryCreds *types.DockerAuthConfig - if len(options.Username) > 0 && len(options.Password) > 0 { - registryCreds = &types.DockerAuthConfig{ - Username: options.Username, - Password: options.Password, - } + listImagesOptions := &libimage.ListImagesOptions{} + if options.All { + listImagesOptions.Filters = []string{"readonly=false"} } - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, - DockerCertPath: options.CertDir, - OSChoice: options.OS, - ArchitectureChoice: options.Arch, - VariantChoice: options.Variant, - DockerInsecureSkipTLSVerify: options.SkipTLSVerify, + images, err := ir.Libpod.LibimageRuntime().ListImages(ctx, nameOrIDs, listImagesOptions) + if err != nil { + return nil, err } - if !options.AllTags { - newImage, err := runtime.New(ctx, rawImage, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, label, options.PullPolicy, nil) + unmountReports := []*entities.ImageUnmountReport{} + for _, image := range images { + r := &entities.ImageUnmountReport{Id: image.ID()} + mountPoint, err := image.Mountpoint() if err != nil { - return nil, err + r.Err = err + unmountReports = append(unmountReports, r) + continue } - return &entities.ImagePullReport{Images: []string{newImage.ID()}}, nil - } - - // --all-tags requires the docker transport - if imageRef.Transport().Name() != docker.Transport.Name() { - return nil, errors.New("--all-tags requires docker transport") + if mountPoint == "" { + // Skip if the image wasn't mounted. + continue + } + r.Err = image.Unmount(options.Force) + unmountReports = append(unmountReports, r) } + return unmountReports, nil +} - // Trim the docker-transport prefix. - rawImage = strings.TrimPrefix(rawImage, docker.Transport.Name()) +func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entities.ImagePullOptions) (*entities.ImagePullReport, error) { + pullOptions := &libimage.PullOptions{AllTags: options.AllTags} + pullOptions.AuthFilePath = options.Authfile + pullOptions.CertDirPath = options.CertDir + pullOptions.Username = options.Username + pullOptions.Password = options.Password + pullOptions.Architecture = options.Arch + pullOptions.OS = options.OS + pullOptions.Variant = options.Variant + pullOptions.SignaturePolicyPath = options.SignaturePolicy + pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify - // all-tags doesn't work with a tagged reference, so let's check early - namedRef, err := reference.Parse(rawImage) - if err != nil { - return nil, errors.Wrapf(err, "error parsing %q", rawImage) - } - if _, isTagged := namedRef.(reference.Tagged); isTagged { - return nil, errors.New("--all-tags requires a reference without a tag") + if !options.Quiet { + pullOptions.Writer = os.Stderr } - systemContext := image.GetSystemContext("", options.Authfile, false) - tags, err := docker.GetRepositoryTags(ctx, systemContext, imageRef) + pulledImages, err := ir.Libpod.LibimageRuntime().Pull(ctx, rawImage, options.PullPolicy, pullOptions) if err != nil { - return nil, errors.Wrapf(err, "error getting repository tags") + return nil, err } - foundIDs := []string{} - for _, tag := range tags { - name := rawImage + ":" + tag - newImage, err := runtime.New(ctx, name, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways, nil) - if err != nil { - logrus.Errorf("error pulling image %q", name) - continue - } - foundIDs = append(foundIDs, newImage.ID()) + pulledIDs := make([]string, len(pulledImages)) + for i := range pulledImages { + pulledIDs[i] = pulledImages[i].ID() } - if len(tags) != len(foundIDs) { - return nil, err - } - return &entities.ImagePullReport{Images: foundIDs}, nil -} - -func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entities.ImagePullOptions) (*entities.ImagePullReport, error) { - return pull(ctx, ir.Libpod.ImageRuntime(), rawImage, options, nil) + return &entities.ImagePullReport{Images: pulledIDs}, nil } func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts entities.InspectOptions) ([]*entities.ImageInspectReport, []error, error) { reports := []*entities.ImageInspectReport{} errs := []error{} for _, i := range namesOrIDs { - img, err := ir.Libpod.ImageRuntime().NewFromLocal(i) + img, _, err := ir.Libpod.LibimageRuntime().LookupImage(i, &libimage.LookupImageOptions{IgnorePlatform: true}) if err != nil { // This is probably a no such image, treat as nonfatal. errs = append(errs, err) continue } - result, err := img.Inspect(ctx) + result, err := img.Inspect(ctx, true) if err != nil { // This is more likely to be fatal. return nil, nil, err @@ -323,11 +282,6 @@ func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts en } func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, options entities.ImagePushOptions) error { - var writer io.Writer - if !options.Quiet { - writer = os.Stderr - } - var manifestType string switch options.Format { case "": @@ -342,58 +296,56 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri return errors.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", options.Format) } - var registryCreds *types.DockerAuthConfig - if len(options.Username) > 0 && len(options.Password) > 0 { - registryCreds = &types.DockerAuthConfig{ - Username: options.Username, - Password: options.Password, - } - } - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, - DockerCertPath: options.CertDir, - DockerInsecureSkipTLSVerify: options.SkipTLSVerify, - } + pushOptions := &libimage.PushOptions{} + pushOptions.AuthFilePath = options.Authfile + pushOptions.CertDirPath = options.CertDir + pushOptions.DirForceCompress = options.Compress + pushOptions.Username = options.Username + pushOptions.Password = options.Password + pushOptions.ManifestMIMEType = manifestType + pushOptions.RemoveSignatures = options.RemoveSignatures + pushOptions.SignBy = options.SignBy + pushOptions.InsecureSkipTLSVerify = options.SkipTLSVerify - signOptions := image.SigningOptions{ - RemoveSignatures: options.RemoveSignatures, - SignBy: options.SignBy, + if !options.Quiet { + pushOptions.Writer = os.Stderr } - newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(source) - if err != nil { - return err - } + pushedManifestBytes, pushError := ir.Libpod.LibimageRuntime().Push(ctx, source, destination, pushOptions) + if pushError == nil { + if options.DigestFile != "" { + manifestDigest, err := manifest.Digest(pushedManifestBytes) + if err != nil { + return err + } - err = newImage.PushImageToHeuristicDestination( - ctx, - destination, - manifestType, - options.Authfile, - options.DigestFile, - options.SignaturePolicy, - writer, - options.Compress, - signOptions, - &dockerRegistryOptions, - nil, - options.Progress) - if err != nil && errors.Cause(err) != storage.ErrImageUnknown { + if err := ioutil.WriteFile(options.DigestFile, []byte(manifestDigest.String()), 0644); err != nil { + return err + } + } + return nil + } + // If the image could not be found, we may be referring to a manifest + // list but could not find a matching image instance in the local + // containers storage. In that case, fall back and attempt to push the + // (entire) manifest. + if errors.Cause(pushError) == storage.ErrImageUnknown { // Image might be a manifest list so attempt a manifest push - if _, manifestErr := ir.ManifestPush(ctx, source, destination, options); manifestErr == nil { + _, manifestErr := ir.ManifestPush(ctx, source, destination, options) + if manifestErr == nil { return nil } } - return err + return pushError } func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, options entities.ImageTagOptions) error { - newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, &libimage.LookupImageOptions{IgnorePlatform: true}) if err != nil { return err } for _, tag := range tags { - if err := newImage.TagImage(tag); err != nil { + if err := image.Tag(tag); err != nil { return err } } @@ -401,54 +353,71 @@ func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, } func (ir *ImageEngine) Untag(ctx context.Context, nameOrID string, tags []string, options entities.ImageUntagOptions) error { - newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, &libimage.LookupImageOptions{IgnorePlatform: true}) if err != nil { return err } // If only one arg is provided, all names are to be untagged if len(tags) == 0 { - tags = newImage.Names() + tags = image.Names() } for _, tag := range tags { - if err := newImage.UntagImage(tag); err != nil { + if err := image.Untag(tag); err != nil { return err } } return nil } -func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions) (*entities.ImageLoadReport, error) { - var ( - writer io.Writer - ) - if !opts.Quiet { - writer = os.Stderr - } - name, err := ir.Libpod.LoadImage(ctx, opts.Input, writer, opts.SignaturePolicy) - if err != nil { - return nil, err +func (ir *ImageEngine) Load(ctx context.Context, options entities.ImageLoadOptions) (*entities.ImageLoadReport, error) { + loadOptions := &libimage.LoadOptions{} + loadOptions.SignaturePolicyPath = options.SignaturePolicy + if !options.Quiet { + loadOptions.Writer = os.Stderr } - return &entities.ImageLoadReport{Names: strings.Split(name, ",")}, nil -} -func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOptions) (*entities.ImageImportReport, error) { - id, err := ir.Libpod.Import(ctx, opts.Source, opts.Reference, opts.SignaturePolicy, opts.Changes, opts.Message, opts.Quiet) + loadedImages, err := ir.Libpod.LibimageRuntime().Load(ctx, options.Input, loadOptions) if err != nil { return nil, err } - return &entities.ImageImportReport{Id: id}, nil + return &entities.ImageLoadReport{Names: loadedImages}, nil } func (ir *ImageEngine) Save(ctx context.Context, nameOrID string, tags []string, options entities.ImageSaveOptions) error { + saveOptions := &libimage.SaveOptions{} + saveOptions.DirForceCompress = options.Compress + saveOptions.RemoveSignatures = options.RemoveSignatures + + if !options.Quiet { + saveOptions.Writer = os.Stderr + } + + names := []string{nameOrID} if options.MultiImageArchive { - nameOrIDs := append([]string{nameOrID}, tags...) - return ir.Libpod.ImageRuntime().SaveImages(ctx, nameOrIDs, options.Format, options.Output, options.Quiet, true) + names = append(names, tags...) + } else { + saveOptions.AdditionalTags = tags } - newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) + return ir.Libpod.LibimageRuntime().Save(ctx, names, options.Format, options.Output, saveOptions) +} + +func (ir *ImageEngine) Import(ctx context.Context, options entities.ImageImportOptions) (*entities.ImageImportReport, error) { + importOptions := &libimage.ImportOptions{} + importOptions.Changes = options.Changes + importOptions.CommitMessage = options.Message + importOptions.Tag = options.Reference + importOptions.SignaturePolicyPath = options.SignaturePolicy + + if !options.Quiet { + importOptions.Writer = os.Stderr + } + + imageID, err := ir.Libpod.LibimageRuntime().Import(ctx, options.Source, importOptions) if err != nil { - return err + return nil, err } - return newImage.Save(ctx, nameOrID, options.Format, options.Output, tags, options.Quiet, options.Compress, true) + + return &entities.ImageImportReport{Id: imageID}, nil } func (ir *ImageEngine) Diff(_ context.Context, nameOrID string, _ entities.DiffOptions) (*entities.DiffReport, error) { @@ -460,12 +429,12 @@ func (ir *ImageEngine) Diff(_ context.Context, nameOrID string, _ entities.DiffO } func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) { - filter, err := image.ParseSearchFilter(opts.Filters) + filter, err := libimage.ParseSearchFilter(opts.Filters) if err != nil { return nil, err } - searchOpts := image.SearchOptions{ + searchOptions := &libimage.SearchOptions{ Authfile: opts.Authfile, Filter: *filter, Limit: opts.Limit, @@ -474,7 +443,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im ListTags: opts.ListTags, } - searchResults, err := image.SearchImages(term, searchOpts) + searchResults, err := ir.Libpod.LibimageRuntime().Search(ctx, term, searchOptions) if err != nil { return nil, err } @@ -510,15 +479,15 @@ func (ir *ImageEngine) Build(ctx context.Context, containerFiles []string, opts } func (ir *ImageEngine) Tree(ctx context.Context, nameOrID string, opts entities.ImageTreeOptions) (*entities.ImageTreeReport, error) { - img, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, &libimage.LookupImageOptions{IgnorePlatform: true}) if err != nil { return nil, err } - results, err := img.GenerateTree(opts.WhatRequires) + tree, err := image.Tree(opts.WhatRequires) if err != nil { return nil, err } - return &entities.ImageTreeReport{Tree: results}, nil + return &entities.ImageTreeReport{Tree: tree}, nil } // removeErrorsToExitCode returns an exit code for the specified slice of @@ -542,9 +511,9 @@ func removeErrorsToExitCode(rmErrors []error) int { for _, e := range rmErrors { switch errors.Cause(e) { - case define.ErrNoSuchImage: + case storage.ErrImageUnknown, storage.ErrLayerUnknown: noSuchImageErrors = true - case define.ErrImageInUse, storage.ErrImageUsedByContainer: + case storage.ErrImageUsedByContainer: inUseErrors = true default: otherErrors = true @@ -574,84 +543,25 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie report.ExitCode = removeErrorsToExitCode(rmErrors) }() - // deleteImage is an anonymous function to conveniently delete an image - // without having to pass all local data around. - deleteImage := func(img *image.Image) error { - results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force) - switch errors.Cause(err) { - case nil: - // Removal worked, so let's report it. - report.Deleted = append(report.Deleted, results.Deleted) - report.Untagged = append(report.Untagged, results.Untagged...) - return nil - case storage.ErrImageUnknown, storage.ErrLayerUnknown: - // The image must have been removed already (see #6510) - // or the storage is corrupted (see #9617). - report.Deleted = append(report.Deleted, img.ID()) - report.Untagged = append(report.Untagged, img.ID()) - return nil - default: - // Fatal error. - return err - } + libimageOptions := &libimage.RemoveImagesOptions{} + libimageOptions.Filters = []string{"readonly=false"} + libimageOptions.Force = opts.Force + if !opts.All { + libimageOptions.Filters = append(libimageOptions.Filters, "intermediate=false") } + libimageOptions.RemoveContainerFunc = ir.Libpod.RemoveContainersForImageCallback(ctx) - // Delete all images from the local storage. - if opts.All { - previousImages := 0 - // Remove all images one-by-one. - for { - storageImages, err := ir.Libpod.ImageRuntime().GetRWImages() - if err != nil { - rmErrors = append(rmErrors, err) - return - } - // No images (left) to remove, so we're done. - if len(storageImages) == 0 { - return - } - // Prevent infinity loops by making a delete-progress check. - if previousImages == len(storageImages) { - rmErrors = append(rmErrors, errors.New("unable to delete all images, check errors and re-run image removal if needed")) - break - } - previousImages = len(storageImages) - // Delete all "leaves" (i.e., images without child images). - for _, img := range storageImages { - isParent, err := img.IsParent(ctx) - if err != nil { - logrus.Warnf("%v, ignoring the error", err) - isParent = false - } - // Skip parent images. - if isParent { - continue - } - if err := deleteImage(img); err != nil { - rmErrors = append(rmErrors, err) - } - } - } - - return - } + libimageReport, libimageErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, images, libimageOptions) - // Delete only the specified images. - for _, id := range images { - img, err := ir.Libpod.ImageRuntime().NewFromLocal(id) - if err != nil { - // attempt to remove image from storage - if forceErr := ir.Libpod.RemoveImageFromStorage(id); forceErr == nil { - continue - } - rmErrors = append(rmErrors, err) - continue - } - err = deleteImage(img) - if err != nil { - rmErrors = append(rmErrors, err) + for _, r := range libimageReport { + if r.Removed { + report.Deleted = append(report.Deleted, r.ID) } + report.Untagged = append(report.Untagged, r.Untagged...) } + + rmErrors = libimageErrors + return //nolint } diff --git a/pkg/domain/infra/abi/images_list.go b/pkg/domain/infra/abi/images_list.go index 3b8aabeb7..b0e947991 100644 --- a/pkg/domain/infra/abi/images_list.go +++ b/pkg/domain/infra/abi/images_list.go @@ -3,23 +3,25 @@ package abi import ( "context" - libpodImage "github.com/containers/podman/v3/libpod/image" + "github.com/containers/common/libimage" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/pkg/errors" ) func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) ([]*entities.ImageSummary, error) { - images, err := ir.Libpod.ImageRuntime().GetImagesWithFilters(opts.Filter) - if err != nil { - return nil, err + listImagesOptions := &libimage.ListImagesOptions{ + Filters: opts.Filter, } - if !opts.All { - filter, err := ir.Libpod.ImageRuntime().IntermediateFilter(ctx, images) - if err != nil { - return nil, err - } - images = libpodImage.FilterImages(images, []libpodImage.ResultFilter{filter}) + // Filter intermediate images unless we want to list *all*. + // NOTE: it's a positive filter, so `intermediate=false` means + // to display non-intermediate images. + listImagesOptions.Filters = append(listImagesOptions.Filters, "intermediate=false") + } + + images, err := ir.Libpod.LibimageRuntime().ListImages(ctx, nil, listImagesOptions) + if err != nil { + return nil, err } summaries := []*entities.ImageSummary{} @@ -30,24 +32,21 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) } e := entities.ImageSummary{ - ID: img.ID(), - ConfigDigest: string(img.ConfigDigest), - Created: img.Created().Unix(), - Dangling: img.Dangling(), - Digest: string(img.Digest()), - RepoDigests: digests, - History: img.NamesHistory(), - Names: img.Names(), - ReadOnly: img.IsReadOnly(), - SharedSize: 0, - RepoTags: img.Names(), // may include tags and digests + ID: img.ID(), + // ConfigDigest: string(img.ConfigDigest), + Created: img.Created().Unix(), + Dangling: img.IsDangling(), + Digest: string(img.Digest()), + RepoDigests: digests, + History: img.NamesHistory(), + Names: img.Names(), + ReadOnly: img.IsReadOnly(), + SharedSize: 0, + RepoTags: img.Names(), // may include tags and digests } e.Labels, err = img.Labels(ctx) if err != nil { - // Ignore empty manifest lists. - if errors.Cause(err) != libpodImage.ErrImageIsBareList { - return nil, errors.Wrapf(err, "error retrieving label for image %q: you may need to remove the image to resolve the error", img.ID()) - } + return nil, errors.Wrapf(err, "error retrieving label for image %q: you may need to remove the image to resolve the error", img.ID()) } ctnrs, err := img.Containers() @@ -56,20 +55,22 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) } e.Containers = len(ctnrs) - sz, err := img.Size(ctx) + sz, err := img.Size() if err != nil { return nil, errors.Wrapf(err, "error retrieving size of image %q: you may need to remove the image to resolve the error", img.ID()) } - e.Size = int64(*sz) + e.Size = sz // This is good enough for now, but has to be // replaced later with correct calculation logic - e.VirtualSize = int64(*sz) + e.VirtualSize = sz - parent, err := img.ParentID(ctx) + parent, err := img.Parent(ctx) if err != nil { return nil, errors.Wrapf(err, "error retrieving parent of image %q: you may need to remove the image to resolve the error", img.ID()) } - e.ParentId = parent + if parent != nil { + e.ParentId = parent.ID() + } summaries = append(summaries, &e) } diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go index 8e3c50fac..f932cf21d 100644 --- a/pkg/domain/infra/abi/manifest.go +++ b/pkg/domain/infra/abi/manifest.go @@ -4,21 +4,17 @@ import ( "bytes" "context" "encoding/json" - "fmt" "os" "strings" - "github.com/containers/buildah/manifests" - buildahManifests "github.com/containers/buildah/pkg/manifests" - buildahUtil "github.com/containers/buildah/util" + "github.com/containers/common/libimage" cp "github.com/containers/image/v5/copy" - "github.com/containers/image/v5/docker" "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/pkg/shortnames" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" - "github.com/containers/image/v5/types" - libpodImage "github.com/containers/podman/v3/libpod/image" "github.com/containers/podman/v3/pkg/domain/entities" + "github.com/containers/storage" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -26,51 +22,103 @@ import ( ) // ManifestCreate implements logic for creating manifest lists via ImageEngine -func (ir *ImageEngine) ManifestCreate(ctx context.Context, names, images []string, opts entities.ManifestCreateOptions) (string, error) { - fullNames, err := buildahUtil.ExpandNames(names, "", ir.Libpod.SystemContext(), ir.Libpod.GetStore()) - if err != nil { - return "", errors.Wrapf(err, "error encountered while expanding image name %q", names) +func (ir *ImageEngine) ManifestCreate(ctx context.Context, names []string, images []string, opts entities.ManifestCreateOptions) (string, error) { + // FIXME: change the interface of manifest create `names []string` -> + // `name string`. + if len(names) == 0 { + return "", errors.New("no name specified for creating a manifest list") } - imageID, err := libpodImage.CreateManifestList(ir.Libpod.ImageRuntime(), *ir.Libpod.SystemContext(), fullNames, images, opts.All) + name := names[0] + + manifestList, err := ir.Libpod.LibimageRuntime().CreateManifestList(name) if err != nil { - return imageID, err + return "", err } - return imageID, err + + addOptions := &libimage.ManifestListAddOptions{All: opts.All} + for _, image := range images { + if _, err := manifestList.Add(ctx, image, addOptions); err != nil { + return "", err + } + } + + return manifestList.ID(), nil } // ManifestExists checks if a manifest list with the given name exists in local storage func (ir *ImageEngine) ManifestExists(ctx context.Context, name string) (*entities.BoolReport, error) { - if image, err := ir.Libpod.ImageRuntime().NewFromLocal(name); err == nil { - exists, err := image.ExistsManifest() - if err != nil && errors.Cause(err) != buildahManifests.ErrManifestTypeNotSupported { - return nil, err + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(name, &libimage.LookupImageOptions{IgnorePlatform: true}) + if err != nil { + if errors.Cause(err) == storage.ErrImageUnknown { + return &entities.BoolReport{Value: false}, nil } - return &entities.BoolReport{Value: exists}, nil + return nil, err + } + + isManifestList, err := image.IsManifestList(ctx) + if err != nil { + return nil, err } - return &entities.BoolReport{Value: false}, nil + return &entities.BoolReport{Value: isManifestList}, nil } // ManifestInspect returns the content of a manifest list or image func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte, error) { - if newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(name); err == nil { - // return the manifest in local storage - if list, err := newImage.InspectManifest(); err == nil { - buf, err := json.MarshalIndent(list, "", " ") - if err != nil { - return buf, errors.Wrapf(err, "error rendering manifest %s for display", name) - } - return buf, nil - // no return if local image is not a list of images type - // continue on getting valid manifest through remote service - } else if errors.Cause(err) != buildahManifests.ErrManifestTypeNotSupported { - return nil, errors.Wrapf(err, "loading manifest %q", name) + // NOTE: we have to do a bit of a limbo here as `podman manifest + // inspect foo` wants to do a remote-inspect of foo iff "foo" in the + // containers storage is an ordinary image but not a manifest list. + + lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(name, lookupOptions) + if err != nil { + // If the image doesn't exist, do a remote inspect. + if errors.Cause(err) == storage.ErrImageUnknown { + return ir.remoteManifestInspect(ctx, name) } + return nil, err + } + + isManifestList, err := image.IsManifestList(ctx) + if err != nil { + return nil, err + } + + // If the image isn't a manifest list, do a remote inspect. + if !isManifestList { + return ir.remoteManifestInspect(ctx, name) } - sc := ir.Libpod.SystemContext() - refs, err := buildahUtil.ResolveNameToReferences(ir.Libpod.GetStore(), sc, name) + + manifestList, err := image.ToManifestList() if err != nil { return nil, err } + + schema2List, err := manifestList.Inspect() + if err != nil { + return nil, err + } + + rawSchema2List, err := json.Marshal(schema2List) + if err != nil { + return nil, err + } + + var b bytes.Buffer + if err := json.Indent(&b, rawSchema2List, "", " "); err != nil { + return nil, errors.Wrapf(err, "error rendering manifest %s for display", name) + } + return b.Bytes(), nil +} + +// inspect a remote manifest list. +func (ir *ImageEngine) remoteManifestInspect(ctx context.Context, name string) ([]byte, error) { + sys := ir.Libpod.SystemContext() + + resolved, err := shortnames.Resolve(sys, name) + if err != nil { + return nil, err + } + var ( latestErr error result []byte @@ -84,8 +132,13 @@ func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte latestErr = errors.Wrapf(latestErr, "tried %v\n", e) } } - for _, ref := range refs { - src, err := ref.NewImageSource(ctx, sc) + + for _, candidate := range resolved.PullCandidates { + ref, err := alltransports.ParseImageName("docker://" + candidate.Value.String()) + if err != nil { + return nil, err + } + src, err := ref.NewImageSource(ctx, sys) if err != nil { appendErr(errors.Wrapf(err, "reading image %q", transports.ImageName(ref))) continue @@ -102,6 +155,7 @@ func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte manType = manifestType break } + if len(result) == 0 && latestErr != nil { return nil, latestErr } @@ -138,29 +192,41 @@ func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte // ManifestAdd adds images to the manifest list func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAddOptions) (string, error) { - imageSpec := opts.Images[0] - listImageSpec := opts.Images[1] - dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name()) - _, err := alltransports.ParseImageName(imageSpec) + // FIXME: the name options below are *mandatory* arguments and should + // be reflected as such in the signature. + + if len(opts.Images) < 2 { + return "", errors.New("manifest add requires two images") + } + + imageName := opts.Images[0] + listName := opts.Images[1] + + manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(listName) if err != nil { - _, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, imageSpec)) - if err != nil { - return "", errors.Errorf("invalid image reference %q", imageSpec) - } + return "", err } - listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(listImageSpec) + + addOptions := &libimage.ManifestListAddOptions{ + All: opts.All, + AuthFilePath: opts.Authfile, + CertDirPath: opts.CertDir, + InsecureSkipTLSVerify: opts.SkipTLSVerify, + Username: opts.Username, + Password: opts.Password, + } + + instanceDigest, err := manifestList.Add(ctx, imageName, addOptions) if err != nil { - return "", errors.Wrapf(err, "error retrieving local image from image name %s", listImageSpec) + return "", err } - manifestAddOpts := libpodImage.ManifestAddOpts{ - All: opts.All, - Arch: opts.Arch, - Features: opts.Features, - Images: opts.Images, - OS: opts.OS, - OSVersion: opts.OSVersion, - Variant: opts.Variant, + annotateOptions := &libimage.ManifestListAnnotateOptions{ + Architecture: opts.Arch, + Features: opts.Features, + OS: opts.OS, + OSVersion: opts.OSVersion, + Variant: opts.Variant, } if len(opts.Annotation) != 0 { annotations := make(map[string]string) @@ -171,51 +237,44 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAd } annotations[spec[0]] = spec[1] } - manifestAddOpts.Annotation = annotations - } - - // Set the system context. - sys := ir.Libpod.SystemContext() - if sys != nil { - sys = &types.SystemContext{} + annotateOptions.Annotations = annotations } - sys.AuthFilePath = opts.Authfile - sys.DockerInsecureSkipTLSVerify = opts.SkipTLSVerify - sys.DockerCertPath = opts.CertDir - if opts.Username != "" && opts.Password != "" { - sys.DockerAuthConfig = &types.DockerAuthConfig{ - Username: opts.Username, - Password: opts.Password, - } + if err := manifestList.AnnotateInstance(instanceDigest, annotateOptions); err != nil { + return "", err } - listID, err := listImage.AddManifest(*sys, manifestAddOpts) - if err != nil { - return listID, err - } - return listID, nil + return manifestList.ID(), nil } // ManifestAnnotate updates an entry of the manifest list func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, names []string, opts entities.ManifestAnnotateOptions) (string, error) { - listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(names[0]) - if err != nil { - return "", errors.Wrapf(err, "error retrieving local image from image name %s", names[0]) + // FIXME: the `names` are *mandatory* arguments and should be + // reflected as such in the signature. + + if len(names) < 2 { + return "", errors.New("manifest annotate requires two names") } - digest, err := digest.Parse(names[1]) + + listName := names[0] + instanceDigest, err := digest.Parse(names[1]) if err != nil { return "", errors.Errorf(`invalid image digest "%s": %v`, names[1], err) } - manifestAnnotateOpts := libpodImage.ManifestAnnotateOpts{ - Arch: opts.Arch, - Features: opts.Features, - OS: opts.OS, - OSFeatures: opts.OSFeatures, - OSVersion: opts.OSVersion, - Variant: opts.Variant, + + manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(listName) + if err != nil { + return "", err + } + + annotateOptions := &libimage.ManifestListAnnotateOptions{ + Architecture: opts.Arch, + Features: opts.Features, + OS: opts.OS, + OSVersion: opts.OSVersion, + Variant: opts.Variant, } - if len(opts.Annotation) > 0 { + if len(opts.Annotation) != 0 { annotations := make(map[string]string) for _, annotationSpec := range opts.Annotation { spec := strings.SplitN(annotationSpec, "=", 2) @@ -224,48 +283,49 @@ func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, names []string, opt } annotations[spec[0]] = spec[1] } - manifestAnnotateOpts.Annotation = annotations + annotateOptions.Annotations = annotations } - updatedListID, err := listImage.AnnotateManifest(*ir.Libpod.SystemContext(), digest, manifestAnnotateOpts) - if err == nil { - return fmt.Sprintf("%s: %s", updatedListID, digest.String()), nil + + if err := manifestList.AnnotateInstance(instanceDigest, annotateOptions); err != nil { + return "", err } - return "", err + + return manifestList.ID(), nil } // ManifestRemove removes specified digest from the specified manifest list func (ir *ImageEngine) ManifestRemove(ctx context.Context, names []string) (string, error) { + // FIXME: the `names` are *mandatory* arguments and should be + // reflected as such in the signature. + + if len(names) < 2 { + return "", errors.New("manifest remove requires two names") + } + + listName := names[0] instanceDigest, err := digest.Parse(names[1]) if err != nil { return "", errors.Errorf(`invalid image digest "%s": %v`, names[1], err) } - listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(names[0]) + + manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(listName) if err != nil { - return "", errors.Wrapf(err, "error retrieving local image from image name %s", names[0]) + return "", err } - updatedListID, err := listImage.RemoveManifest(instanceDigest) - if err == nil { - return fmt.Sprintf("%s :%s\n", updatedListID, instanceDigest.String()), nil + + if err := manifestList.RemoveInstance(instanceDigest); err != nil { + return "", err } - return "", err + + return manifestList.ID(), nil } // ManifestPush pushes a manifest list or image index to the destination func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination string, opts entities.ImagePushOptions) (string, error) { - listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(name) + manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name) if err != nil { return "", errors.Wrapf(err, "error retrieving local image from image name %s", name) } - dest, err := alltransports.ParseImageName(destination) - if err != nil { - oldErr := err - // Try adding the images default transport - destination2 := libpodImage.DefaultTransport + destination - dest, err = alltransports.ParseImageName(destination2) - if err != nil { - return "", oldErr - } - } var manifestType string if opts.Format != "" { @@ -279,40 +339,33 @@ func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination strin } } - // Set the system context. - sys := ir.Libpod.SystemContext() - if sys == nil { - sys = new(types.SystemContext) - } - sys.AuthFilePath = opts.Authfile - sys.DockerInsecureSkipTLSVerify = opts.SkipTLSVerify - sys.DockerCertPath = opts.CertDir - - if opts.Username != "" && opts.Password != "" { - sys.DockerAuthConfig = &types.DockerAuthConfig{ - Username: opts.Username, - Password: opts.Password, - } - } + pushOptions := &libimage.ManifestListPushOptions{} + pushOptions.AuthFilePath = opts.Authfile + pushOptions.CertDirPath = opts.CertDir + pushOptions.Username = opts.Username + pushOptions.Password = opts.Password + pushOptions.ImageListSelection = cp.CopySpecificImages + pushOptions.ManifestMIMEType = manifestType + pushOptions.RemoveSignatures = opts.RemoveSignatures + pushOptions.SignBy = opts.SignBy - options := manifests.PushOptions{ - Store: ir.Libpod.GetStore(), - SystemContext: sys, - ImageListSelection: cp.CopySpecificImages, - Instances: nil, - RemoveSignatures: opts.RemoveSignatures, - SignBy: opts.SignBy, - ManifestType: manifestType, - } if opts.All { - options.ImageListSelection = cp.CopyAllImages + pushOptions.ImageListSelection = cp.CopyAllImages } if !opts.Quiet { - options.ReportWriter = os.Stderr + pushOptions.Writer = os.Stderr } - manDigest, err := listImage.PushManifest(dest, options) - if err == nil && opts.Rm { - _, err = ir.Libpod.GetStore().DeleteImage(listImage.ID(), true) + + manDigest, err := manifestList.Push(ctx, destination, pushOptions) + if err != nil { + return "", err + } + + if opts.Rm { + if _, err := ir.Libpod.GetStore().DeleteImage(manifestList.ID(), true); err != nil { + return "", errors.Wrap(err, "error removing manifest after push") + } } + return manDigest.String(), err } diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index 6ddd4a042..d235c9ed8 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -10,17 +10,17 @@ import ( "strconv" "strings" + "github.com/containers/common/libimage" + "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/secrets" "github.com/containers/image/v5/types" "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/domain/entities" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/specgen/generate" "github.com/containers/podman/v3/pkg/specgen/generate/kube" "github.com/containers/podman/v3/pkg/util" - "github.com/docker/distribution/reference" "github.com/ghodss/yaml" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -154,10 +154,9 @@ func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAM func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec, options entities.PlayKubeOptions, ipIndex *int) (*entities.PlayKubeReport, error) { var ( - registryCreds *types.DockerAuthConfig - writer io.Writer - playKubePod entities.PlayKubePod - report entities.PlayKubeReport + writer io.Writer + playKubePod entities.PlayKubePod + report entities.PlayKubeReport ) // Create the secret manager before hand @@ -220,19 +219,6 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY writer = os.Stderr } - if len(options.Username) > 0 && len(options.Password) > 0 { - registryCreds = &types.DockerAuthConfig{ - Username: options.Username, - Password: options.Password, - } - } - - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, - DockerCertPath: options.CertDir, - DockerInsecureSkipTLSVerify: options.SkipTLSVerify, - } - volumes, err := kube.InitializeVolumes(podYAML.Spec.Volumes) if err != nil { return nil, err @@ -273,35 +259,36 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY containers := make([]*libpod.Container, 0, len(podYAML.Spec.Containers)) for _, container := range podYAML.Spec.Containers { - pullPolicy := util.PullImageMissing + // NOTE: set the pull policy to "newer". This will cover cases + // where the "latest" tag requires a pull and will also + // transparently handle "localhost/" prefixed files which *may* + // refer to a locally built image OR an image running a + // registry on localhost. + pullPolicy := config.PullPolicyNewer if len(container.ImagePullPolicy) > 0 { - pullPolicy, err = util.ValidatePullType(string(container.ImagePullPolicy)) + pullPolicy, err = config.ParsePullPolicy(string(container.ImagePullPolicy)) if err != nil { return nil, err } } - named, err := reference.ParseNormalizedNamed(container.Image) - if err != nil { - return nil, errors.Wrapf(err, "Failed to parse image %q", container.Image) - } - // In kube, if the image is tagged with latest, it should always pull - // but if the domain is localhost, that means the image was built locally - // so do not attempt a pull. - if tagged, isTagged := named.(reference.NamedTagged); isTagged { - if tagged.Tag() == image.LatestTag && reference.Domain(named) != image.DefaultLocalRegistry { - pullPolicy = util.PullImageAlways - } - } - // This ensures the image is the image store - newImage, err := ic.Libpod.ImageRuntime().New(ctx, container.Image, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, pullPolicy, nil) + pullOptions := &libimage.PullOptions{} + pullOptions.AuthFilePath = options.Authfile + pullOptions.CertDirPath = options.CertDir + pullOptions.SignaturePolicyPath = options.SignaturePolicy + pullOptions.Writer = writer + pullOptions.Username = options.Username + pullOptions.Password = options.Password + pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify + + pulledImages, err := ic.Libpod.LibimageRuntime().Pull(ctx, container.Image, pullPolicy, pullOptions) if err != nil { return nil, err } specgenOpts := kube.CtrSpecGenOptions{ Container: container, - Image: newImage, + Image: pulledImages[0], Volumes: volumes, PodID: pod.ID(), PodName: podName, diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 9bba0fa6c..ebe59e871 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -164,7 +164,10 @@ func movePauseProcessToScope(r *libpod.Runtime) error { // SystemPrune removes unused data from the system. Pruning pods, containers, volumes and images. func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.SystemPruneOptions) (*entities.SystemPruneReport, error) { var systemPruneReport = new(entities.SystemPruneReport) - var filters []string + filters := []string{} + for k, v := range options.Filters { + filters = append(filters, fmt.Sprintf("%s=%s", k, v[0])) + } reclaimedSpace := (uint64)(0) found := true for found { @@ -188,10 +191,12 @@ func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.Sys } reclaimedSpace = reclaimedSpace + reports.PruneReportsSize(containerPruneReports) systemPruneReport.ContainerPruneReports = append(systemPruneReport.ContainerPruneReports, containerPruneReports...) - for k, v := range options.Filters { - filters = append(filters, fmt.Sprintf("%s=%s", k, v[0])) + imagePruneOptions := entities.ImagePruneOptions{ + All: options.All, + Filter: filters, } - imagePruneReports, err := ic.Libpod.ImageRuntime().PruneImages(ctx, options.All, filters) + imageEngine := ImageEngine{Libpod: ic.Libpod} + imagePruneReports, err := imageEngine.Prune(ctx, imagePruneOptions) reclaimedSpace = reclaimedSpace + reports.PruneReportsSize(imagePruneReports) if err != nil { @@ -225,13 +230,7 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System dfImages = []*entities.SystemDfImageReport{} ) - // Compute disk-usage stats for all local images. - imgs, err := ic.Libpod.ImageRuntime().GetImages() - if err != nil { - return nil, err - } - - imageStats, err := ic.Libpod.ImageRuntime().DiskUsage(ctx, imgs) + imageStats, err := ic.Libpod.LibimageRuntime().DiskUsage(ctx) if err != nil { return nil, err } diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 90b6e104b..3fd9a755d 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -8,10 +8,10 @@ import ( "strings" "time" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/types" - "github.com/containers/podman/v3/libpod/image" images "github.com/containers/podman/v3/pkg/bindings/images" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/domain/entities/reports" @@ -311,7 +311,7 @@ func (ir *ImageEngine) Diff(ctx context.Context, nameOrID string, _ entities.Dif func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) { mappedFilters := make(map[string][]string) - filters, err := image.ParseSearchFilter(opts.Filters) + filters, err := libimage.ParseSearchFilter(opts.Filters) if err != nil { return nil, err } -- cgit v1.2.3-54-g00ecf