summaryrefslogtreecommitdiff
path: root/libpod/image
diff options
context:
space:
mode:
authorOpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com>2021-05-05 09:20:16 -0400
committerGitHub <noreply@github.com>2021-05-05 09:20:16 -0400
commita278195af3423f882c1f5bb218792f7c2ce10a3c (patch)
tree192e52054de2abf0c92d83ecdbc71d498c2ec947 /libpod/image
parent8eefca5a257121b177562742c972e39e1686140d (diff)
parent0f7d54b0260c1be992ee3b9cee359ef3a9e8bd21 (diff)
downloadpodman-a278195af3423f882c1f5bb218792f7c2ce10a3c.tar.gz
podman-a278195af3423f882c1f5bb218792f7c2ce10a3c.tar.bz2
podman-a278195af3423f882c1f5bb218792f7c2ce10a3c.zip
Merge pull request #10147 from vrothberg/new-image-package
migrate Podman to containers/common/libimage
Diffstat (limited to 'libpod/image')
-rw-r--r--libpod/image/config.go14
-rw-r--r--libpod/image/df.go126
-rw-r--r--libpod/image/docker_registry_options.go75
-rw-r--r--libpod/image/errors.go16
-rw-r--r--libpod/image/filters.go196
-rw-r--r--libpod/image/image.go1858
-rw-r--r--libpod/image/image_test.go318
-rw-r--r--libpod/image/layer_tree.go239
-rw-r--r--libpod/image/manifests.go209
-rw-r--r--libpod/image/parts.go104
-rw-r--r--libpod/image/parts_test.go123
-rw-r--r--libpod/image/prune.go164
-rw-r--r--libpod/image/pull.go437
-rw-r--r--libpod/image/pull_test.go394
-rw-r--r--libpod/image/search.go318
-rw-r--r--libpod/image/signing_options.go10
-rw-r--r--libpod/image/testdata/docker-name-only.tar.xzbin1024 -> 0 bytes
-rw-r--r--libpod/image/testdata/docker-registry-name.tar.xzbin1028 -> 0 bytes
-rw-r--r--libpod/image/testdata/docker-two-images.tar.xzbin1416 -> 0 bytes
-rw-r--r--libpod/image/testdata/docker-two-names.tar.xzbin1040 -> 0 bytes
-rw-r--r--libpod/image/testdata/docker-unnamed.tar.xzbin968 -> 0 bytes
-rw-r--r--libpod/image/testdata/oci-name-only.tar.gzbin975 -> 0 bytes
-rw-r--r--libpod/image/testdata/oci-non-docker-name.tar.gzbin991 -> 0 bytes
-rw-r--r--libpod/image/testdata/oci-registry-name.tar.gzbin979 -> 0 bytes
-rw-r--r--libpod/image/testdata/oci-unnamed.tar.gzbin928 -> 0 bytes
-rw-r--r--libpod/image/testdata/registries.conf4
-rw-r--r--libpod/image/tree.go138
-rw-r--r--libpod/image/utils.go182
28 files changed, 0 insertions, 4925 deletions
diff --git a/libpod/image/config.go b/libpod/image/config.go
deleted file mode 100644
index efd83d343..000000000
--- a/libpod/image/config.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package image
-
-const (
- // LatestTag describes the tag used to refer to the latest version
- // of an image
- LatestTag = "latest"
-)
-
-// ImageDeleteResponse is the response for removing an image from storage and containers
-// what was untagged vs actually removed
-type ImageDeleteResponse struct { //nolint
- Untagged []string `json:"untagged"`
- Deleted string `json:"deleted"`
-}
diff --git a/libpod/image/df.go b/libpod/image/df.go
deleted file mode 100644
index 231d28df4..000000000
--- a/libpod/image/df.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package image
-
-import (
- "context"
- "time"
-
- "github.com/containers/image/v5/docker/reference"
-)
-
-// DiskUsageStat gives disk-usage statistics for a specific image.
-type DiskUsageStat struct {
- // ID of the image.
- ID string
- // Repository of the first recorded name of the image.
- Repository string
- // Tag of the first recorded name of the image.
- Tag string
- // Created is the creation time of the image.
- Created time.Time
- // SharedSize is the amount of space shared with another image.
- SharedSize uint64
- // UniqueSize is the amount of space used only by this image.
- UniqueSize uint64
- // Size is the total size of the image (i.e., the sum of the shared and
- // unique size).
- Size uint64
- // Number of containers using the image.
- Containers int
-}
-
-// DiskUsage returns disk-usage statistics for the specified slice of images.
-func (ir *Runtime) DiskUsage(ctx context.Context, images []*Image) ([]DiskUsageStat, error) {
- stats := make([]DiskUsageStat, len(images))
-
- // Build a layerTree to quickly compute (and cache!) parent/child
- // relations.
- tree, err := ir.layerTree()
- if err != nil {
- return nil, err
- }
-
- // Calculate the stats for each image.
- for i, img := range images {
- stat, err := diskUsageForImage(ctx, img, tree)
- if err != nil {
- return nil, err
- }
- stats[i] = *stat
- }
-
- return stats, nil
-}
-
-// diskUsageForImage returns the disk-usage statistics for the specified image.
-func diskUsageForImage(ctx context.Context, image *Image, tree *layerTree) (*DiskUsageStat, error) {
- stat := DiskUsageStat{
- ID: image.ID(),
- Created: image.Created(),
- }
-
- // Repository and tag.
- var name, repository, tag string
- for _, n := range image.Names() {
- if len(n) > 0 {
- name = n
- break
- }
- }
- if len(name) > 0 {
- named, err := reference.ParseNormalizedNamed(name)
- if err != nil {
- return nil, err
- }
- repository = named.Name()
- if tagged, isTagged := named.(reference.NamedTagged); isTagged {
- tag = tagged.Tag()
- }
- } else {
- repository = "<none>"
- tag = "<none>"
- }
- stat.Repository = repository
- stat.Tag = tag
-
- // Shared, unique and total size.
- parent, err := tree.parent(ctx, image)
- if err != nil {
- return nil, err
- }
- childIDs, err := tree.children(ctx, image, false)
- if err != nil {
- return nil, err
- }
- // Optimistically set unique size to the full size of the image.
- size, err := image.Size(ctx)
- if err != nil {
- return nil, err
- }
- stat.UniqueSize = *size
-
- if len(childIDs) > 0 {
- // If we have children, we share everything.
- stat.SharedSize = stat.UniqueSize
- stat.UniqueSize = 0
- } else if parent != nil {
- // If we have no children but a parent, remove the parent
- // (shared) size from the unique one.
- size, err := parent.Size(ctx)
- if err != nil {
- return nil, err
- }
- stat.UniqueSize -= *size
- stat.SharedSize = *size
- }
-
- stat.Size = stat.SharedSize + stat.UniqueSize
-
- // Number of containers using the image.
- containers, err := image.Containers()
- if err != nil {
- return nil, err
- }
- stat.Containers = len(containers)
-
- return &stat, nil
-}
diff --git a/libpod/image/docker_registry_options.go b/libpod/image/docker_registry_options.go
deleted file mode 100644
index d95234e3d..000000000
--- a/libpod/image/docker_registry_options.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package image
-
-import (
- "fmt"
-
- "github.com/containers/buildah/pkg/parse"
- "github.com/containers/image/v5/docker/reference"
- "github.com/containers/image/v5/types"
- podmanVersion "github.com/containers/podman/v3/version"
-)
-
-// DockerRegistryOptions encapsulates settings that affect how we connect or
-// authenticate to a remote registry.
-type DockerRegistryOptions struct {
- // DockerRegistryCreds is the user name and password to supply in case
- // we need to pull an image from a registry, and it requires us to
- // authenticate.
- DockerRegistryCreds *types.DockerAuthConfig
- // DockerCertPath is the location of a directory containing CA
- // certificates which will be used to verify the registry's certificate
- // (all files with names ending in ".crt"), and possibly client
- // certificates and private keys (pairs of files with the same name,
- // except for ".cert" and ".key" suffixes).
- DockerCertPath string
- // DockerInsecureSkipTLSVerify turns off verification of TLS
- // certificates and allows connecting to registries without encryption
- // - or forces it on even if registries.conf has the registry configured as insecure.
- DockerInsecureSkipTLSVerify types.OptionalBool
- // If not "", overrides the use of platform.GOOS when choosing an image or verifying OS match.
- OSChoice string
- // If not "", overrides the use of platform.GOARCH when choosing an image or verifying architecture match.
- ArchitectureChoice string
- // If not "", overrides_VARIANT_ instead of the running architecture variant for choosing images.
- VariantChoice string
- // RegistriesConfPath can be used to override the default path of registries.conf.
- RegistriesConfPath string
-}
-
-// GetSystemContext constructs a new system context from a parent context. the values in the DockerRegistryOptions, and other parameters.
-func (o DockerRegistryOptions) GetSystemContext(parent *types.SystemContext, additionalDockerArchiveTags []reference.NamedTagged) *types.SystemContext {
- sc := &types.SystemContext{
- DockerAuthConfig: o.DockerRegistryCreds,
- DockerCertPath: o.DockerCertPath,
- DockerInsecureSkipTLSVerify: o.DockerInsecureSkipTLSVerify,
- DockerArchiveAdditionalTags: additionalDockerArchiveTags,
- OSChoice: o.OSChoice,
- ArchitectureChoice: o.ArchitectureChoice,
- VariantChoice: o.VariantChoice,
- BigFilesTemporaryDir: parse.GetTempDir(),
- }
- if parent != nil {
- sc.SignaturePolicyPath = parent.SignaturePolicyPath
- sc.AuthFilePath = parent.AuthFilePath
- sc.DirForceCompress = parent.DirForceCompress
- sc.DockerRegistryUserAgent = parent.DockerRegistryUserAgent
- sc.OSChoice = parent.OSChoice
- sc.ArchitectureChoice = parent.ArchitectureChoice
- sc.BlobInfoCacheDir = parent.BlobInfoCacheDir
- }
- return sc
-}
-
-// GetSystemContext Constructs a new containers/image/types.SystemContext{} struct from the given signaturePolicy path
-func GetSystemContext(signaturePolicyPath, authFilePath string, forceCompress bool) *types.SystemContext {
- sc := &types.SystemContext{}
- if signaturePolicyPath != "" {
- sc.SignaturePolicyPath = signaturePolicyPath
- }
- sc.AuthFilePath = authFilePath
- sc.DirForceCompress = forceCompress
- sc.DockerRegistryUserAgent = fmt.Sprintf("libpod/%s", podmanVersion.Version)
- sc.BigFilesTemporaryDir = parse.GetTempDir()
-
- return sc
-}
diff --git a/libpod/image/errors.go b/libpod/image/errors.go
deleted file mode 100644
index 49f841bf4..000000000
--- a/libpod/image/errors.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package image
-
-import (
- "github.com/containers/podman/v3/libpod/define"
-)
-
-var (
- // ErrNoSuchCtr indicates the requested container does not exist
- ErrNoSuchCtr = define.ErrNoSuchCtr
- // ErrNoSuchPod indicates the requested pod does not exist
- ErrNoSuchPod = define.ErrNoSuchPod
- // ErrNoSuchImage indicates the requested image does not exist
- ErrNoSuchImage = define.ErrNoSuchImage
- // ErrNoSuchTag indicates the requested image tag does not exist
- ErrNoSuchTag = define.ErrNoSuchTag
-)
diff --git a/libpod/image/filters.go b/libpod/image/filters.go
deleted file mode 100644
index d316c6956..000000000
--- a/libpod/image/filters.go
+++ /dev/null
@@ -1,196 +0,0 @@
-package image
-
-import (
- "context"
- "fmt"
- "path/filepath"
- "strconv"
- "strings"
- "time"
-
- "github.com/containers/podman/v3/pkg/inspect"
- "github.com/containers/podman/v3/pkg/util"
- "github.com/pkg/errors"
- "github.com/sirupsen/logrus"
-)
-
-// ResultFilter is a mock function for image filtering
-type ResultFilter func(*Image) bool
-
-// Filter is a function to determine whether an image is included in
-// command output. Images to be outputted are tested using the function. A true
-// return will include the image, a false return will exclude it.
-type Filter func(*Image, *inspect.ImageData) bool
-
-// CreatedBeforeFilter allows you to filter on images created before
-// the given time.Time
-func CreatedBeforeFilter(createTime time.Time) ResultFilter {
- return func(i *Image) bool {
- return i.Created().Before(createTime)
- }
-}
-
-// IntermediateFilter returns filter for intermediate images (i.e., images
-// with children and no tags).
-func (ir *Runtime) IntermediateFilter(ctx context.Context, images []*Image) (ResultFilter, error) {
- tree, err := ir.layerTree()
- if err != nil {
- return nil, err
- }
- return func(i *Image) bool {
- if len(i.Names()) > 0 {
- return true
- }
- children, err := tree.children(ctx, i, false)
- if err != nil {
- logrus.Error(err.Error())
- return false
- }
- return len(children) == 0
- }, nil
-}
-
-// CreatedAfterFilter allows you to filter on images created after
-// the given time.Time
-func CreatedAfterFilter(createTime time.Time) ResultFilter {
- return func(i *Image) bool {
- return i.Created().After(createTime)
- }
-}
-
-// DanglingFilter allows you to filter images for dangling images
-func DanglingFilter(danglingImages bool) ResultFilter {
- return func(i *Image) bool {
- if danglingImages {
- return i.Dangling()
- }
- return !i.Dangling()
- }
-}
-
-// ReadOnlyFilter allows you to filter images based on read/only and read/write
-func ReadOnlyFilter(readOnly bool) ResultFilter {
- return func(i *Image) bool {
- if readOnly {
- return i.IsReadOnly()
- }
- return !i.IsReadOnly()
- }
-}
-
-// LabelFilter allows you to filter by images labels key and/or value
-func LabelFilter(ctx context.Context, filter string) ResultFilter {
- // We need to handle both label=key and label=key=value
- return func(i *Image) bool {
- labels, err := i.Labels(ctx)
- if err != nil {
- return false
- }
- return util.MatchLabelFilters([]string{filter}, labels)
- }
-}
-
-// ReferenceFilter allows you to filter by image name
-// Replacing all '/' with '|' so that filepath.Match() can work
-// '|' character is not valid in image name, so this is safe
-func ReferenceFilter(ctx context.Context, referenceFilter string) ResultFilter {
- filter := fmt.Sprintf("*%s*", referenceFilter)
- filter = strings.Replace(filter, "/", "|", -1)
- return func(i *Image) bool {
- if len(referenceFilter) < 1 {
- return true
- }
- for _, name := range i.Names() {
- newName := strings.Replace(name, "/", "|", -1)
- match, err := filepath.Match(filter, newName)
- if err != nil {
- logrus.Errorf("failed to match %s and %s, %q", name, referenceFilter, err)
- }
- if match {
- return true
- }
- }
- return false
- }
-}
-
-// IDFilter allows you to filter by image Id
-func IDFilter(idFilter string) ResultFilter {
- return func(i *Image) bool {
- return i.ID() == idFilter
- }
-}
-
-// OutputImageFilter allows you to filter by an a specific image name
-func OutputImageFilter(userImage *Image) ResultFilter {
- return func(i *Image) bool {
- return userImage.ID() == i.ID()
- }
-}
-
-// FilterImages filters images using a set of predefined filter funcs
-func FilterImages(images []*Image, filters []ResultFilter) []*Image {
- var filteredImages []*Image
- for _, image := range images {
- include := true
- for _, filter := range filters {
- include = include && filter(image)
- }
- if include {
- filteredImages = append(filteredImages, image)
- }
- }
- return filteredImages
-}
-
-// createFilterFuncs returns an array of filter functions based on the user inputs
-// and is later used to filter images for output
-func (ir *Runtime) createFilterFuncs(filters []string, img *Image) ([]ResultFilter, error) {
- var filterFuncs []ResultFilter
- ctx := context.Background()
- for _, filter := range filters {
- splitFilter := strings.SplitN(filter, "=", 2)
- if len(splitFilter) < 2 {
- return nil, errors.Errorf("invalid filter syntax %s", filter)
- }
- switch splitFilter[0] {
- case "before":
- before, err := ir.NewFromLocal(splitFilter[1])
- if err != nil {
- return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1])
- }
- filterFuncs = append(filterFuncs, CreatedBeforeFilter(before.Created()))
- case "since", "after":
- after, err := ir.NewFromLocal(splitFilter[1])
- if err != nil {
- return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1])
- }
- filterFuncs = append(filterFuncs, CreatedAfterFilter(after.Created()))
- case "readonly":
- readonly, err := strconv.ParseBool(splitFilter[1])
- if err != nil {
- return nil, errors.Wrapf(err, "invalid filter readonly=%s", splitFilter[1])
- }
- filterFuncs = append(filterFuncs, ReadOnlyFilter(readonly))
- case "dangling":
- danglingImages, err := strconv.ParseBool(splitFilter[1])
- if err != nil {
- return nil, errors.Wrapf(err, "invalid filter dangling=%s", splitFilter[1])
- }
- filterFuncs = append(filterFuncs, DanglingFilter(danglingImages))
- case "label":
- labelFilter := strings.Join(splitFilter[1:], "=")
- filterFuncs = append(filterFuncs, LabelFilter(ctx, labelFilter))
- case "reference":
- filterFuncs = append(filterFuncs, ReferenceFilter(ctx, splitFilter[1]))
- case "id":
- filterFuncs = append(filterFuncs, IDFilter(splitFilter[1]))
- default:
- return nil, errors.Errorf("invalid filter %s ", splitFilter[0])
- }
- }
- if img != nil {
- filterFuncs = append(filterFuncs, OutputImageFilter(img))
- }
- return filterFuncs, nil
-}
diff --git a/libpod/image/image.go b/libpod/image/image.go
deleted file mode 100644
index 3c9fb3a37..000000000
--- a/libpod/image/image.go
+++ /dev/null
@@ -1,1858 +0,0 @@
-package image
-
-import (
- "context"
- "encoding/json"
- stderrors "errors"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path/filepath"
- "sort"
- "strings"
- "syscall"
- "time"
-
- "github.com/containers/common/pkg/retry"
- cp "github.com/containers/image/v5/copy"
- "github.com/containers/image/v5/directory"
- dockerarchive "github.com/containers/image/v5/docker/archive"
- "github.com/containers/image/v5/docker/reference"
- "github.com/containers/image/v5/image"
- "github.com/containers/image/v5/manifest"
- ociarchive "github.com/containers/image/v5/oci/archive"
- "github.com/containers/image/v5/oci/layout"
- "github.com/containers/image/v5/pkg/shortnames"
- is "github.com/containers/image/v5/storage"
- "github.com/containers/image/v5/tarball"
- "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/driver"
- "github.com/containers/podman/v3/libpod/events"
- "github.com/containers/podman/v3/pkg/inspect"
- "github.com/containers/podman/v3/pkg/registries"
- "github.com/containers/podman/v3/pkg/util"
- "github.com/containers/storage"
- digest "github.com/opencontainers/go-digest"
- ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
- "github.com/pkg/errors"
- "github.com/sirupsen/logrus"
-)
-
-// Image is the primary struct for dealing with images
-// It is still very much a work in progress
-type Image struct {
- // Adding these two structs for now but will cull when we near
- // completion of this library.
- imgRef types.Image
- imgSrcRef types.ImageSource
- inspect.ImageData
- inspect.ImageResult
- inspectInfo *types.ImageInspectInfo
- InputName string
- image *storage.Image
- imageruntime *Runtime
-}
-
-// Runtime contains the store
-type Runtime struct {
- store storage.Store
- SignaturePolicyPath string
- EventsLogFilePath string
- EventsLogger string
- Eventer events.Eventer
-}
-
-// InfoImage keep information of Image along with all associated layers
-type InfoImage struct {
- // ID of image
- ID string
- // Tags of image
- Tags []string
- // Layers stores all layers of image.
- Layers []LayerInfo
-}
-
-const maxRetry = 3
-
-// ImageFilter is a function to determine whether a image is included
-// in command output. Images to be outputted are tested using the function.
-// A true return will include the image, a false return will exclude it.
-type ImageFilter func(*Image) bool //nolint
-
-// ErrRepoTagNotFound is the error returned when the image id given doesn't match a rep tag in store
-var ErrRepoTagNotFound = stderrors.New("unable to match user input to any specific repotag")
-
-// ErrImageIsBareList is the error returned when the image is just a list or index
-var ErrImageIsBareList = stderrors.New("image contains a manifest list or image index, but no runnable image")
-
-// NewImageRuntimeFromStore creates an ImageRuntime based on a provided store
-func NewImageRuntimeFromStore(store storage.Store) *Runtime {
- return &Runtime{
- store: store,
- }
-}
-
-// NewImageRuntimeFromOptions creates an Image Runtime including the store given
-// store options
-func NewImageRuntimeFromOptions(options storage.StoreOptions) (*Runtime, error) {
- store, err := setStore(options)
- if err != nil {
- return nil, err
- }
- return NewImageRuntimeFromStore(store), nil
-}
-
-func setStore(options storage.StoreOptions) (storage.Store, error) {
- store, err := storage.GetStore(options)
- if err != nil {
- return nil, err
- }
- is.Transport.SetStore(store)
- return store, nil
-}
-
-// newImage creates a new image object given an "input name" and a storage.Image
-func (ir *Runtime) newImage(inputName string, img *storage.Image) *Image {
- return &Image{
- InputName: inputName,
- imageruntime: ir,
- image: img,
- }
-}
-
-// newFromStorage creates a new image object from a storage.Image. Its "input name" will be its ID.
-func (ir *Runtime) newFromStorage(img *storage.Image) *Image {
- return ir.newImage(img.ID, img)
-}
-
-// NewFromLocal creates a new image object that is intended
-// to only deal with local images already in the store (or
-// its aliases)
-func (ir *Runtime) NewFromLocal(name string) (*Image, error) {
- updatedInputName, localImage, err := ir.getLocalImage(name)
- if err != nil {
- return nil, err
- }
- return ir.newImage(updatedInputName, localImage), nil
-}
-
-// New creates a new image object where the image could be local
-// or remote
-func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *DockerRegistryOptions, signingoptions SigningOptions, label *string, pullType util.PullType, progress chan types.ProgressProperties) (*Image, error) {
- // We don't know if the image is local or not ... check local first
- if pullType != util.PullImageAlways {
- newImage, err := ir.NewFromLocal(name)
- if err == nil {
- return newImage, nil
- } else if pullType == util.PullImageNever {
- return nil, err
- }
- }
-
- // The image is not local
- if signaturePolicyPath == "" {
- signaturePolicyPath = ir.SignaturePolicyPath
- }
- imageName, err := ir.pullImageFromHeuristicSource(ctx, name, writer, authfile, signaturePolicyPath, signingoptions, dockeroptions, &retry.RetryOptions{MaxRetry: maxRetry}, label, progress)
- if err != nil {
- return nil, err
- }
-
- newImage, err := ir.NewFromLocal(imageName[0])
- if err != nil {
- return nil, errors.Wrapf(err, "error retrieving local image after pulling %s", name)
- }
- return newImage, nil
-}
-
-// SaveImages stores one more images in a multi-image archive.
-// Note that only `docker-archive` supports storing multiple
-// image.
-func (ir *Runtime) SaveImages(ctx context.Context, namesOrIDs []string, format string, outputFile string, quiet, removeSignatures bool) (finalErr error) {
- if format != DockerArchive {
- return errors.Errorf("multi-image archives are only supported in in the %q format", DockerArchive)
- }
-
- sys := GetSystemContext("", "", false)
-
- archWriter, err := dockerarchive.NewWriter(sys, outputFile)
- if err != nil {
- return err
- }
- defer func() {
- err := archWriter.Close()
- if err == nil {
- return
- }
- if finalErr == nil {
- finalErr = err
- return
- }
- finalErr = errors.Wrap(finalErr, err.Error())
- }()
-
- // Decide whether c/image's progress bars should use stderr or stdout.
- // Use stderr in case we need to be quiet or if the output is set to
- // stdout. If the output is set of stdout, any log message there would
- // corrupt the tarfile.
- writer := os.Stdout
- if quiet {
- writer = os.Stderr
- }
-
- // extend an image with additional tags
- type imageData struct {
- *Image
- tags []reference.NamedTagged
- }
-
- // Look up the images (and their tags) in the local storage.
- imageMap := make(map[string]*imageData) // to group tags for an image
- imageQueue := []string{} // to preserve relative image order
- for _, nameOrID := range namesOrIDs {
- // Look up the name or ID in the local image storage.
- localImage, err := ir.NewFromLocal(nameOrID)
- if err != nil {
- return err
- }
- id := localImage.ID()
-
- iData, exists := imageMap[id]
- if !exists {
- imageQueue = append(imageQueue, id)
- iData = &imageData{Image: localImage}
- imageMap[id] = iData
- }
-
- // Unless we referred to an ID, add the input as a tag.
- if !strings.HasPrefix(id, nameOrID) {
- tag, err := NormalizedTag(nameOrID)
- if err != nil {
- return err
- }
- refTagged, isTagged := tag.(reference.NamedTagged)
- if isTagged {
- iData.tags = append(iData.tags, refTagged)
- }
- }
- }
-
- policyContext, err := getPolicyContext(sys)
- if err != nil {
- return err
- }
- defer func() {
- if err := policyContext.Destroy(); err != nil {
- logrus.Errorf("failed to destroy policy context: %q", err)
- }
- }()
-
- // Now copy the images one-by-one.
- for _, id := range imageQueue {
- dest, err := archWriter.NewReference(nil)
- if err != nil {
- return err
- }
-
- img := imageMap[id]
- copyOptions := getCopyOptions(sys, writer, nil, nil, SigningOptions{RemoveSignatures: removeSignatures}, "", img.tags)
- copyOptions.DestinationCtx.SystemRegistriesConfPath = registries.SystemRegistriesConfPath()
-
- // For copying, we need a source reference that we can create
- // from the image.
- src, err := is.Transport.NewStoreReference(img.imageruntime.store, nil, id)
- if err != nil {
- return errors.Wrapf(err, "error getting source imageReference for %q", img.InputName)
- }
- _, err = cp.Image(ctx, policyContext, dest, src, copyOptions)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-// LoadAllImagesFromDockerArchive loads all images from the docker archive that
-// fileName points to.
-func (ir *Runtime) LoadAllImagesFromDockerArchive(ctx context.Context, fileName string, signaturePolicyPath string, writer io.Writer) ([]*Image, error) {
- if signaturePolicyPath == "" {
- signaturePolicyPath = ir.SignaturePolicyPath
- }
-
- sc := GetSystemContext(signaturePolicyPath, "", false)
- reader, err := dockerarchive.NewReader(sc, fileName)
- if err != nil {
- return nil, err
- }
-
- defer func() {
- if err := reader.Close(); err != nil {
- logrus.Errorf(err.Error())
- }
- }()
-
- refLists, err := reader.List()
- if err != nil {
- return nil, err
- }
-
- refPairs := []pullRefPair{}
- for _, refList := range refLists {
- for _, ref := range refList {
- pairs, err := ir.getPullRefPairsFromDockerArchiveReference(ctx, reader, ref, sc)
- if err != nil {
- return nil, err
- }
- refPairs = append(refPairs, pairs...)
- }
- }
-
- goal := pullGoal{
- pullAllPairs: true,
- refPairs: refPairs,
- }
-
- defer goal.cleanUp()
- imageNames, err := ir.doPullImage(ctx, sc, goal, writer, SigningOptions{}, &DockerRegistryOptions{}, &retry.RetryOptions{}, nil, nil)
- if err != nil {
- return nil, err
- }
-
- newImages := make([]*Image, 0, len(imageNames))
- for _, name := range imageNames {
- newImage, err := ir.NewFromLocal(name)
- if err != nil {
- return nil, errors.Wrapf(err, "error retrieving local image after pulling %s", name)
- }
- newImages = append(newImages, newImage)
- }
- ir.newImageEvent(events.LoadFromArchive, "")
- return newImages, nil
-}
-
-// LoadFromArchiveReference creates a new image object for images pulled from a tar archive and the like (podman load)
-// This function is needed because it is possible for a tar archive to have multiple tags for one image
-func (ir *Runtime) LoadFromArchiveReference(ctx context.Context, srcRef types.ImageReference, signaturePolicyPath string, writer io.Writer) ([]*Image, error) {
- if signaturePolicyPath == "" {
- signaturePolicyPath = ir.SignaturePolicyPath
- }
-
- imageNames, err := ir.pullImageFromReference(ctx, srcRef, writer, "", signaturePolicyPath, SigningOptions{}, &DockerRegistryOptions{}, &retry.RetryOptions{})
- if err != nil {
- return nil, errors.Wrapf(err, "unable to pull %s", transports.ImageName(srcRef))
- }
-
- newImages := make([]*Image, 0, len(imageNames))
- for _, name := range imageNames {
- newImage, err := ir.NewFromLocal(name)
- if err != nil {
- return nil, errors.Wrapf(err, "error retrieving local image after pulling %s", name)
- }
- newImages = append(newImages, newImage)
- }
- ir.newImageEvent(events.LoadFromArchive, "")
- return newImages, nil
-}
-
-// Shutdown closes down the storage and require a bool arg as to
-// whether it should do so forcibly.
-func (ir *Runtime) Shutdown(force bool) error {
- _, err := ir.store.Shutdown(force)
- return err
-}
-
-// GetImagesWithFilters gets images with a series of filters applied
-func (ir *Runtime) GetImagesWithFilters(filters []string) ([]*Image, error) {
- filterFuncs, err := ir.createFilterFuncs(filters, nil)
- if err != nil {
- return nil, err
- }
- images, err := ir.GetImages()
- if err != nil {
- return nil, err
- }
- return FilterImages(images, filterFuncs), nil
-}
-
-func (i *Image) reloadImage() error {
- newImage, err := i.imageruntime.getImage(i.ID())
- if err != nil {
- return errors.Wrapf(err, "unable to reload image")
- }
- i.image = newImage
- return nil
-}
-
-// stringSha256 strips sha256 from user input
-func stripSha256(name string) string {
- if strings.HasPrefix(name, "sha256:") && len(name) > 7 {
- return name[7:]
- }
- return name
-}
-
-// getLocalImage resolves an unknown input describing an image and
-// returns an updated input name, and a storage.Image, or an error. It is used by NewFromLocal.
-func (ir *Runtime) getLocalImage(inputName string) (string, *storage.Image, error) {
- imageError := fmt.Sprintf("unable to find '%s' in local storage", inputName)
- if inputName == "" {
- return "", nil, errors.Errorf("input name is blank")
- }
-
- // Check if the input name has a transport and if so strip it
- dest, err := alltransports.ParseImageName(inputName)
- if err == nil && dest.DockerReference() != nil {
- inputName = dest.DockerReference().String()
- }
-
- // Early check for fully-qualified images and (short) IDs.
- img, err := ir.store.Image(stripSha256(inputName))
- if err == nil {
- return inputName, img, nil
- }
-
- // Note that it's crucial to first decompose the image and check if
- // it's a fully-qualified one or a "short name". The latter requires
- // some normalization with search registries and the
- // "localhost/prefix".
- decomposedImage, err := decompose(inputName)
- if err != nil {
- // We may have a storage reference. We can't parse it to a
- // reference before. Otherwise, we'd normalize "alpine" to
- // "docker.io/library/alpine:latest" which would break the
- // order in which we should query local images below.
- if ref, err := is.Transport.ParseStoreReference(ir.store, inputName); err == nil {
- img, err = is.Transport.GetStoreImage(ir.store, ref)
- if err == nil {
- return inputName, img, nil
- }
- }
- return "", nil, err
- }
-
- // The specified image is fully qualified, so it doesn't exist in the
- // storage.
- if decomposedImage.hasRegistry {
- // However ... we may still need to normalize to docker.io:
- // `docker.io/foo` -> `docker.io/library/foo`
- if ref, err := is.Transport.ParseStoreReference(ir.store, inputName); err == nil {
- img, err = is.Transport.GetStoreImage(ir.store, ref)
- if err == nil {
- return inputName, img, nil
- }
- }
- return "", nil, errors.Wrapf(ErrNoSuchImage, imageError)
- }
-
- sys := &types.SystemContext{
- SystemRegistriesConfPath: registries.SystemRegistriesConfPath(),
- }
-
- candidates, err := shortnames.ResolveLocally(sys, inputName)
- if err != nil {
- return "", nil, err
- }
-
- for _, candidate := range candidates {
- img, err := ir.store.Image(candidate.String())
- if err == nil {
- return candidate.String(), img, nil
- }
- }
-
- // Backwards compat: normalize to docker.io as some users may very well
- // rely on that.
- ref, err := is.Transport.ParseStoreReference(ir.store, inputName)
- if err == nil {
- img, err = is.Transport.GetStoreImage(ir.store, ref)
- if err == nil {
- return inputName, img, nil
- }
- }
-
- // Last resort: look at the repotags of all images and try to find a
- // match.
- images, err := ir.GetImages()
- if err != nil {
- return "", nil, err
- }
-
- decomposedImage, err = decompose(inputName)
- if err != nil {
- return "", nil, err
- }
- repoImage, err := findImageInRepotags(decomposedImage, images)
- if err == nil {
- return inputName, repoImage, nil
- }
-
- return "", nil, err
-}
-
-// ID returns the image ID as a string
-func (i *Image) ID() string {
- return i.image.ID
-}
-
-// IsReadOnly returns whether the image ID comes from a local store
-func (i *Image) IsReadOnly() bool {
- return i.image.ReadOnly
-}
-
-// Digest returns the image's digest
-func (i *Image) Digest() digest.Digest {
- return i.image.Digest
-}
-
-// Digests returns the image's digests
-func (i *Image) Digests() []digest.Digest {
- return i.image.Digests
-}
-
-// GetManifest returns the image's manifest as a byte array
-// and manifest type as a string.
-func (i *Image) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
- imgSrcRef, err := i.toImageSourceRef(ctx)
- if err != nil {
- return nil, "", err
- }
- return imgSrcRef.GetManifest(ctx, instanceDigest)
-}
-
-// Manifest returns the image's manifest as a byte array
-// and manifest type as a string.
-func (i *Image) Manifest(ctx context.Context) ([]byte, string, error) {
- imgRef, err := i.toImageRef(ctx)
- if err != nil {
- return nil, "", err
- }
- return imgRef.Manifest(ctx)
-}
-
-// Names returns a string array of names associated with the image, which may be a mixture of tags and digests
-func (i *Image) Names() []string {
- return i.image.Names
-}
-
-// NamesHistory returns a string array of names previously associated with the
-// image, which may be a mixture of tags and digests
-func (i *Image) NamesHistory() []string {
- if len(i.image.Names) > 0 && len(i.image.NamesHistory) > 0 &&
- // We compare the latest (time-referenced) tags for equality and skip
- // it in the history if they match to not display them twice. We have
- // to compare like this, because `i.image.Names` (latest last) gets
- // appended on retag, whereas `i.image.NamesHistory` gets prepended
- // (latest first)
- i.image.Names[len(i.image.Names)-1] == i.image.NamesHistory[0] {
- return i.image.NamesHistory[1:]
- }
- return i.image.NamesHistory
-}
-
-// RepoTags returns a string array of repotags associated with the image
-func (i *Image) RepoTags() ([]string, error) {
- var repoTags []string
- for _, name := range i.Names() {
- named, err := reference.ParseNormalizedNamed(name)
- if err != nil {
- return nil, err
- }
- if tagged, isTagged := named.(reference.NamedTagged); isTagged {
- repoTags = append(repoTags, tagged.String())
- }
- }
- return repoTags, nil
-}
-
-// RepoDigests returns a string array of repodigests associated with the image
-func (i *Image) RepoDigests() ([]string, error) {
- var repoDigests []string
- added := make(map[string]struct{})
-
- for _, name := range i.Names() {
- for _, imageDigest := range append(i.Digests(), i.Digest()) {
- if imageDigest == "" {
- continue
- }
-
- named, err := reference.ParseNormalizedNamed(name)
- if err != nil {
- return nil, err
- }
-
- canonical, err := reference.WithDigest(reference.TrimNamed(named), imageDigest)
- if err != nil {
- return nil, err
- }
-
- if _, alreadyInList := added[canonical.String()]; !alreadyInList {
- repoDigests = append(repoDigests, canonical.String())
- added[canonical.String()] = struct{}{}
- }
- }
- }
- sort.Strings(repoDigests)
- return repoDigests, nil
-}
-
-// Created returns the time the image was created
-func (i *Image) Created() time.Time {
- return i.image.Created
-}
-
-// TopLayer returns the top layer id as a string
-func (i *Image) TopLayer() string {
- return i.image.TopLayer
-}
-
-// Remove an image; container removal for the image must be done
-// outside the context of images
-// TODO: the force param does nothing as of now. Need to move container
-// handling logic here eventually.
-func (i *Image) Remove(ctx context.Context, force bool) error {
- parent, err := i.GetParent(ctx)
- if err != nil {
- logrus.Warnf("error determining parent of image: %v, ignoring the error", err)
- parent = nil
- }
- if _, err := i.imageruntime.store.DeleteImage(i.ID(), true); err != nil {
- return err
- }
- i.newImageEvent(events.Remove)
- for parent != nil {
- nextParent, err := parent.GetParent(ctx)
- if err != nil {
- return err
- }
- children, err := parent.GetChildren(ctx)
- if err != nil {
- return err
- }
- // Do not remove if image is a base image and is not untagged, or if
- // the image has more children.
- if len(children) > 0 || len(parent.Names()) > 0 {
- return nil
- }
- id := parent.ID()
- if _, err := i.imageruntime.store.DeleteImage(id, true); err != nil {
- logrus.Debugf("unable to remove intermediate image %q: %v", id, err)
- } else {
- fmt.Println(id)
- }
- parent = nextParent
- }
- return nil
-}
-
-// getImage retrieves an image matching the given name or hash from system
-// storage
-// If no matching image can be found, an error is returned
-func (ir *Runtime) getImage(image string) (*storage.Image, error) {
- var img *storage.Image
- ref, err := is.Transport.ParseStoreReference(ir.store, image)
- if err == nil {
- img, err = is.Transport.GetStoreImage(ir.store, ref)
- }
- if err != nil {
- img2, err2 := ir.store.Image(image)
- if err2 != nil {
- if ref == nil {
- return nil, errors.Wrapf(err, "error parsing reference to image %q", image)
- }
- return nil, errors.Wrapf(err, "unable to locate image %q", image)
- }
- img = img2
- }
- return img, nil
-}
-
-func (ir *Runtime) ImageNames(id string) ([]string, error) {
- myImage, err := ir.getImage(id)
- if err != nil {
- return nil, errors.Wrapf(err, "error getting image %s ", id)
- }
- return myImage.Names, nil
-}
-
-// GetImages retrieves all images present in storage
-func (ir *Runtime) GetImages() ([]*Image, error) {
- return ir.getImages(false)
-}
-
-// GetRWImages retrieves all read/write images present in storage
-func (ir *Runtime) GetRWImages() ([]*Image, error) {
- return ir.getImages(true)
-}
-
-// getImages retrieves all images present in storage
-func (ir *Runtime) getImages(rwOnly bool) ([]*Image, error) {
- images, err := ir.store.Images()
- if err != nil {
- return nil, err
- }
- newImages := []*Image{}
- for _, i := range images {
- if rwOnly && i.ReadOnly {
- continue
- }
- // iterating over these, be careful to not iterate on the literal
- // pointer.
- image := i
- img := ir.newFromStorage(&image)
- newImages = append(newImages, img)
- }
- return newImages, nil
-}
-
-// getImageDigest creates an image object and uses the hex value of the digest as the image ID
-// for parsing the store reference
-func getImageDigest(ctx context.Context, src types.ImageReference, sc *types.SystemContext) (string, error) {
- newImg, err := src.NewImage(ctx, sc)
- if err != nil {
- return "", err
- }
- defer func() {
- if err := newImg.Close(); err != nil {
- logrus.Errorf("failed to close image: %q", err)
- }
- }()
- imageDigest := newImg.ConfigInfo().Digest
- if err = imageDigest.Validate(); err != nil {
- return "", errors.Wrapf(err, "error getting config info")
- }
- return "@" + imageDigest.Hex(), nil
-}
-
-// NormalizedTag returns the canonical version of tag for use in Image.Names()
-func NormalizedTag(tag string) (reference.Named, error) {
- decomposedTag, err := decompose(tag)
- if err != nil {
- return nil, err
- }
- // If the input doesn't specify a registry, set the registry to localhost
- var ref reference.Named
- if !decomposedTag.hasRegistry {
- ref, err = decomposedTag.referenceWithRegistry(DefaultLocalRegistry)
- if err != nil {
- return nil, err
- }
- } else {
- ref, err = decomposedTag.normalizedReference()
- if err != nil {
- return nil, err
- }
- }
- // If the input does not have a tag, we need to add one (latest)
- ref = reference.TagNameOnly(ref)
- return ref, nil
-}
-
-// TagImage adds a tag to the given image
-func (i *Image) TagImage(tag string) error {
- if err := i.reloadImage(); err != nil {
- return err
- }
- ref, err := NormalizedTag(tag)
- if err != nil {
- return err
- }
- tags := i.Names()
- if util.StringInSlice(ref.String(), tags) {
- return nil
- }
- tags = append(tags, ref.String())
- if err := i.imageruntime.store.SetNames(i.ID(), tags); err != nil {
- return err
- }
- if err := i.reloadImage(); err != nil {
- return err
- }
- i.newImageEvent(events.Tag)
- return nil
-}
-
-// UntagImage removes the specified tag from the image.
-// If the tag does not exist, ErrNoSuchTag is returned.
-func (i *Image) UntagImage(tag string) error {
- if err := i.reloadImage(); err != nil {
- return err
- }
-
- // Normalize the tag as we do with TagImage.
- ref, err := NormalizedTag(tag)
- if err != nil {
- return err
- }
- tag = ref.String()
-
- var newTags []string
- tags := i.Names()
- if !util.StringInSlice(tag, tags) {
- return errors.Wrapf(ErrNoSuchTag, "%q", tag)
- }
- for _, t := range tags {
- if tag != t {
- newTags = append(newTags, t)
- }
- }
- if err := i.imageruntime.store.SetNames(i.ID(), newTags); err != nil {
- return err
- }
- if err := i.reloadImage(); err != nil {
- return err
- }
- i.newImageEvent(events.Untag)
- return nil
-}
-
-// PushImageToHeuristicDestination pushes the given image to "destination", which is heuristically parsed.
-// Use PushImageToReference if the destination is known precisely.
-func (i *Image) PushImageToHeuristicDestination(ctx context.Context, destination, manifestMIMEType, authFile, digestFile, signaturePolicyPath string, writer io.Writer, forceCompress bool, signingOptions SigningOptions, dockerRegistryOptions *DockerRegistryOptions, additionalDockerArchiveTags []reference.NamedTagged, progress chan types.ProgressProperties) error {
- if destination == "" {
- return errors.Wrapf(syscall.EINVAL, "destination image name must be specified")
- }
-
- // Get the destination Image Reference
- dest, err := alltransports.ParseImageName(destination)
- if err != nil {
- if hasTransport(destination) {
- return errors.Wrapf(err, "error getting destination imageReference for %q", destination)
- }
- // Try adding the images default transport
- destination2 := DefaultTransport + destination
- dest, err = alltransports.ParseImageName(destination2)
- if err != nil {
- return err
- }
- }
- return i.PushImageToReference(ctx, dest, manifestMIMEType, authFile, digestFile, signaturePolicyPath, writer, forceCompress, signingOptions, dockerRegistryOptions, additionalDockerArchiveTags, progress)
-}
-
-// PushImageToReference pushes the given image to a location described by the given path
-func (i *Image) PushImageToReference(ctx context.Context, dest types.ImageReference, manifestMIMEType, authFile, digestFile, signaturePolicyPath string, writer io.Writer, forceCompress bool, signingOptions SigningOptions, dockerRegistryOptions *DockerRegistryOptions, additionalDockerArchiveTags []reference.NamedTagged, progress chan types.ProgressProperties) error {
- sc := GetSystemContext(signaturePolicyPath, authFile, forceCompress)
- sc.BlobInfoCacheDir = filepath.Join(i.imageruntime.store.GraphRoot(), "cache")
-
- policyContext, err := getPolicyContext(sc)
- if err != nil {
- return err
- }
- defer func() {
- if err := policyContext.Destroy(); err != nil {
- logrus.Errorf("failed to destroy policy context: %q", err)
- }
- }()
-
- // Look up the source image, expecting it to be in local storage
- src, err := is.Transport.ParseStoreReference(i.imageruntime.store, i.ID())
- if err != nil {
- return errors.Wrapf(err, "error getting source imageReference for %q", i.InputName)
- }
- copyOptions := getCopyOptions(sc, writer, nil, dockerRegistryOptions, signingOptions, manifestMIMEType, additionalDockerArchiveTags)
- copyOptions.DestinationCtx.SystemRegistriesConfPath = registries.SystemRegistriesConfPath() // FIXME: Set this more globally. Probably no reason not to have it in every types.SystemContext, and to compute the value just once in one place.
- if progress != nil {
- copyOptions.Progress = progress
- copyOptions.ProgressInterval = time.Second
- }
- // Copy the image to the remote destination
- manifestBytes, err := cp.Image(ctx, policyContext, dest, src, copyOptions)
- if err != nil {
- return errors.Wrapf(err, "error copying image to the remote destination")
- }
- digest, err := manifest.Digest(manifestBytes)
- if err != nil {
- return errors.Wrapf(err, "error computing digest of manifest of new image %q", transports.ImageName(dest))
- }
-
- logrus.Debugf("Successfully pushed %s with digest %s", transports.ImageName(dest), digest.String())
-
- if digestFile != "" {
- if err = ioutil.WriteFile(digestFile, []byte(digest.String()), 0644); err != nil {
- return errors.Wrapf(err, "failed to write digest to file %q", digestFile)
- }
- }
- i.newImageEvent(events.Push)
- return nil
-}
-
-// MatchesID returns a bool based on if the input id
-// matches the image's id
-// TODO: This isn't used anywhere, so remove it
-func (i *Image) MatchesID(id string) bool {
- return strings.HasPrefix(i.ID(), id)
-}
-
-// ToImageRef returns an image reference type from an image
-// TODO: Hopefully we can remove this exported function for mheon
-func (i *Image) ToImageRef(ctx context.Context) (types.Image, error) {
- return i.toImageRef(ctx)
-}
-
-// toImageSourceRef returns an ImageSource Reference type from an image
-func (i *Image) toImageSourceRef(ctx context.Context) (types.ImageSource, error) {
- if i == nil {
- return nil, errors.Errorf("cannot convert nil image to image source reference")
- }
- if i.imgSrcRef == nil {
- ref, err := is.Transport.ParseStoreReference(i.imageruntime.store, "@"+i.ID())
- if err != nil {
- return nil, errors.Wrapf(err, "error parsing reference to image %q", i.ID())
- }
- imgSrcRef, err := ref.NewImageSource(ctx, nil)
- if err != nil {
- return nil, errors.Wrapf(err, "error reading image %q as image source", i.ID())
- }
- i.imgSrcRef = imgSrcRef
- }
- return i.imgSrcRef, nil
-}
-
-//Size returns the size of the image
-func (i *Image) Size(ctx context.Context) (*uint64, error) {
- sum, err := i.imageruntime.store.ImageSize(i.ID())
- if err == nil && sum >= 0 {
- usum := uint64(sum)
- return &usum, nil
- }
- return nil, errors.Wrap(err, "unable to determine size")
-}
-
-// toImageRef returns an Image Reference type from an image
-func (i *Image) toImageRef(ctx context.Context) (types.Image, error) {
- if i == nil {
- return nil, errors.Errorf("cannot convert nil image to image reference")
- }
- imgSrcRef, err := i.toImageSourceRef(ctx)
- if err != nil {
- return nil, err
- }
- if i.imgRef == nil {
- systemContext := &types.SystemContext{}
- unparsedDefaultInstance := image.UnparsedInstance(imgSrcRef, nil)
- imgRef, err := image.FromUnparsedImage(ctx, systemContext, unparsedDefaultInstance)
- if err != nil {
- // check for a "tried-to-treat-a-bare-list-like-a-runnable-image" problem, else
- // return info about the not-a-bare-list runnable image part of this storage.Image
- if manifestBytes, manifestType, err2 := imgSrcRef.GetManifest(ctx, nil); err2 == nil {
- if manifest.MIMETypeIsMultiImage(manifestType) {
- if list, err3 := manifest.ListFromBlob(manifestBytes, manifestType); err3 == nil {
- switch manifestType {
- case ociv1.MediaTypeImageIndex:
- err = errors.Wrapf(ErrImageIsBareList, "%q is an image index", i.InputName)
- case manifest.DockerV2ListMediaType:
- err = errors.Wrapf(ErrImageIsBareList, "%q is a manifest list", i.InputName)
- default:
- err = errors.Wrapf(ErrImageIsBareList, "%q", i.InputName)
- }
- for _, instanceDigest := range list.Instances() {
- instance := instanceDigest
- unparsedInstance := image.UnparsedInstance(imgSrcRef, &instance)
- if imgRef2, err4 := image.FromUnparsedImage(ctx, systemContext, unparsedInstance); err4 == nil {
- imgRef = imgRef2
- err = nil
- break
- }
- }
- }
- }
- }
- if err != nil {
- return nil, errors.Wrapf(err, "error reading image %q as image", i.ID())
- }
- }
- i.imgRef = imgRef
- }
- return i.imgRef, nil
-}
-
-// DriverData gets the driver data from the store on a layer
-func (i *Image) DriverData() (*define.DriverData, error) {
- return driver.GetDriverData(i.imageruntime.store, i.TopLayer())
-}
-
-// Layer returns the image's top layer
-func (i *Image) Layer() (*storage.Layer, error) {
- return i.imageruntime.store.Layer(i.image.TopLayer)
-}
-
-// History contains the history information of an image
-type History struct {
- ID string `json:"id"`
- Created *time.Time `json:"created"`
- CreatedBy string `json:"createdBy"`
- Size int64 `json:"size"`
- Comment string `json:"comment"`
- Tags []string `json:"tags"`
-}
-
-// History gets the history of an image and the IDs of images that are part of
-// its history
-func (i *Image) History(ctx context.Context) ([]*History, error) {
- img, err := i.toImageRef(ctx)
- if err != nil {
- if errors.Cause(err) == ErrImageIsBareList {
- return nil, nil
- }
- return nil, err
- }
- oci, err := img.OCIConfig(ctx)
- if err != nil {
- return nil, err
- }
-
- // Build a mapping from top-layer to image ID.
- images, err := i.imageruntime.GetImages()
- if err != nil {
- return nil, err
- }
- topLayerMap := make(map[string]string)
- for _, image := range images {
- if _, exists := topLayerMap[image.TopLayer()]; !exists {
- topLayerMap[image.TopLayer()] = image.ID()
- }
- }
-
- var allHistory []*History
- var layer *storage.Layer
-
- // Check if we have an actual top layer to prevent lookup errors.
- if i.TopLayer() != "" {
- layer, err = i.imageruntime.store.Layer(i.TopLayer())
- if err != nil {
- return nil, err
- }
- }
-
- // Iterate in reverse order over the history entries, and lookup the
- // corresponding image ID, size and get the next later if needed.
- numHistories := len(oci.History) - 1
- for x := numHistories; x >= 0; x-- {
- var size int64
-
- id := "<missing>"
- if x == numHistories {
- id = i.ID()
- }
- if layer != nil {
- if !oci.History[x].EmptyLayer {
- size = layer.UncompressedSize
- }
- if imageID, exists := topLayerMap[layer.ID]; exists {
- id = imageID
- // Delete the entry to avoid reusing it for following history items.
- delete(topLayerMap, layer.ID)
- }
- }
- h := History{
- ID: id,
- Created: oci.History[x].Created,
- CreatedBy: oci.History[x].CreatedBy,
- Size: size,
- Comment: oci.History[x].Comment,
- }
- if layer != nil {
- h.Tags = layer.Names
- }
- allHistory = append(allHistory, &h)
-
- if layer != nil && layer.Parent != "" && !oci.History[x].EmptyLayer {
- layer, err = i.imageruntime.store.Layer(layer.Parent)
- if err != nil {
- return nil, err
- }
- }
- }
-
- return allHistory, nil
-}
-
-// Dangling returns a bool if the image is "dangling"
-func (i *Image) Dangling() bool {
- return len(i.Names()) == 0
-}
-
-// User returns the image's user
-func (i *Image) User(ctx context.Context) (string, error) {
- imgInspect, err := i.inspect(ctx, false)
- if err != nil {
- return "", err
- }
- return imgInspect.Config.User, nil
-}
-
-// StopSignal returns the image's StopSignal
-func (i *Image) StopSignal(ctx context.Context) (string, error) {
- imgInspect, err := i.inspect(ctx, false)
- if err != nil {
- return "", err
- }
- return imgInspect.Config.StopSignal, nil
-}
-
-// WorkingDir returns the image's WorkingDir
-func (i *Image) WorkingDir(ctx context.Context) (string, error) {
- imgInspect, err := i.inspect(ctx, false)
- if err != nil {
- return "", err
- }
- return imgInspect.Config.WorkingDir, nil
-}
-
-// Cmd returns the image's cmd
-func (i *Image) Cmd(ctx context.Context) ([]string, error) {
- imgInspect, err := i.inspect(ctx, false)
- if err != nil {
- return nil, err
- }
- return imgInspect.Config.Cmd, nil
-}
-
-// Entrypoint returns the image's entrypoint
-func (i *Image) Entrypoint(ctx context.Context) ([]string, error) {
- imgInspect, err := i.inspect(ctx, false)
- if err != nil {
- return nil, err
- }
- return imgInspect.Config.Entrypoint, nil
-}
-
-// Env returns the image's env
-func (i *Image) Env(ctx context.Context) ([]string, error) {
- imgInspect, err := i.imageInspectInfo(ctx)
- if err != nil {
- return nil, err
- }
- return imgInspect.Env, nil
-}
-
-// Labels returns the image's labels
-func (i *Image) Labels(ctx context.Context) (map[string]string, error) {
- imgInspect, err := i.imageInspectInfo(ctx)
- if err != nil {
- return nil, err
- }
- return imgInspect.Labels, nil
-}
-
-// GetLabel Returns a case-insensitive match of a given label
-func (i *Image) GetLabel(ctx context.Context, label string) (string, error) {
- labels, err := i.Labels(ctx)
- if err != nil {
- return "", err
- }
-
- for k, v := range labels {
- if strings.EqualFold(k, label) {
- return v, nil
- }
- }
- return "", nil
-}
-
-// Annotations returns the annotations of an image
-func (i *Image) Annotations(ctx context.Context) (map[string]string, error) {
- imageManifest, manifestType, err := i.Manifest(ctx)
- if err != nil {
- imageManifest, manifestType, err = i.GetManifest(ctx, nil)
- if err != nil {
- return nil, err
- }
- }
- annotations := make(map[string]string)
- if manifestType == ociv1.MediaTypeImageManifest {
- var m ociv1.Manifest
- if err := json.Unmarshal(imageManifest, &m); err == nil {
- for k, v := range m.Annotations {
- annotations[k] = v
- }
- }
- }
- return annotations, nil
-}
-
-// ociv1Image converts an image to an imgref and then returns its config blob
-// converted to an ociv1 image type
-func (i *Image) ociv1Image(ctx context.Context) (*ociv1.Image, error) {
- imgRef, err := i.toImageRef(ctx)
- if err != nil {
- return nil, err
- }
- return imgRef.OCIConfig(ctx)
-}
-
-func (i *Image) imageInspectInfo(ctx context.Context) (*types.ImageInspectInfo, error) {
- if i.inspectInfo == nil {
- ic, err := i.toImageRef(ctx)
- if err != nil {
- return nil, err
- }
- imgInspect, err := ic.Inspect(ctx)
- if err != nil {
- return nil, err
- }
- i.inspectInfo = imgInspect
- }
- return i.inspectInfo, nil
-}
-
-func (i *Image) inspect(ctx context.Context, calculateSize bool) (*inspect.ImageData, error) {
- ociv1Img, err := i.ociv1Image(ctx)
- if err != nil {
- ociv1Img = &ociv1.Image{}
- }
- info, err := i.imageInspectInfo(ctx)
- if err != nil {
- info = &types.ImageInspectInfo{}
- }
- annotations, err := i.Annotations(ctx)
- if err != nil {
- return nil, err
- }
-
- size := int64(-1)
- if calculateSize {
- if usize, err := i.Size(ctx); err == nil {
- size = int64(*usize)
- }
- }
-
- parent, err := i.ParentID(ctx)
- if err != nil {
- return nil, err
- }
-
- repoTags, err := i.RepoTags()
- if err != nil {
- return nil, err
- }
-
- repoDigests, err := i.RepoDigests()
- if err != nil {
- return nil, err
- }
-
- driver, err := i.DriverData()
- if err != nil {
- return nil, err
- }
-
- _, manifestType, err := i.GetManifest(ctx, nil)
- if err != nil {
- return nil, errors.Wrapf(err, "unable to determine manifest type")
- }
- comment, err := i.Comment(ctx, manifestType)
- if err != nil {
- return nil, err
- }
-
- data := &inspect.ImageData{
- ID: i.ID(),
- Parent: parent,
- RepoTags: repoTags,
- RepoDigests: repoDigests,
- Comment: comment,
- Created: ociv1Img.Created,
- Author: ociv1Img.Author,
- Architecture: ociv1Img.Architecture,
- Os: ociv1Img.OS,
- Config: &ociv1Img.Config,
- Version: info.DockerVersion,
- Size: size,
- // This is good enough for now, but has to be
- // replaced later with correct calculation logic
- VirtualSize: size,
- Annotations: annotations,
- Digest: i.Digest(),
- Labels: info.Labels,
- RootFS: &inspect.RootFS{
- Type: ociv1Img.RootFS.Type,
- Layers: ociv1Img.RootFS.DiffIDs,
- },
- GraphDriver: driver,
- ManifestType: manifestType,
- User: ociv1Img.Config.User,
- History: ociv1Img.History,
- NamesHistory: i.NamesHistory(),
- }
- if manifestType == manifest.DockerV2Schema2MediaType {
- hc, err := i.GetHealthCheck(ctx)
- if err != nil {
- return nil, err
- }
- if hc != nil {
- data.HealthCheck = hc
- }
- }
- return data, nil
-}
-
-// Inspect returns an image's inspect data
-func (i *Image) Inspect(ctx context.Context) (*inspect.ImageData, error) {
- return i.inspect(ctx, true)
-}
-
-// InspectNoSize returns an image's inspect data without calculating the size for the image
-func (i *Image) InspectNoSize(ctx context.Context) (*inspect.ImageData, error) {
- return i.inspect(ctx, false)
-}
-
-// Import imports and image into the store and returns an image
-func (ir *Runtime) Import(ctx context.Context, path, reference string, writer io.Writer, signingOptions SigningOptions, imageConfig ociv1.Image) (*Image, error) {
- src, err := tarball.Transport.ParseReference(path)
- if err != nil {
- return nil, errors.Wrapf(err, "error parsing image name %q", path)
- }
-
- updater, ok := src.(tarball.ConfigUpdater)
- if !ok {
- return nil, errors.Wrapf(err, "unexpected type, a tarball reference should implement tarball.ConfigUpdater")
- }
-
- annotations := make(map[string]string)
-
- // config ociv1.Image
- err = updater.ConfigUpdate(imageConfig, annotations)
- if err != nil {
- return nil, errors.Wrapf(err, "error updating image config")
- }
-
- sc := GetSystemContext(ir.SignaturePolicyPath, "", false)
-
- // if reference not given, get the image digest
- if reference == "" {
- reference, err = getImageDigest(ctx, src, sc)
- if err != nil {
- return nil, err
- }
- }
- policyContext, err := getPolicyContext(sc)
- if err != nil {
- return nil, err
- }
- defer func() {
- if err := policyContext.Destroy(); err != nil {
- logrus.Errorf("failed to destroy policy context: %q", err)
- }
- }()
- copyOptions := getCopyOptions(sc, writer, nil, nil, signingOptions, "", nil)
- dest, err := is.Transport.ParseStoreReference(ir.store, reference)
- if err != nil {
- return nil, errors.Wrapf(err, "error getting image reference for %q", reference)
- }
- _, err = cp.Image(ctx, policyContext, dest, src, copyOptions)
- if err != nil {
- return nil, err
- }
- newImage, err := ir.NewFromLocal(reference)
- if err == nil {
- newImage.newImageEvent(events.Import)
- }
- return newImage, err
-}
-
-// MatchRepoTag takes a string and tries to match it against an
-// image's repotags
-func (i *Image) MatchRepoTag(input string) (string, error) {
- results := make(map[int][]string)
- var maxCount int
- // first check if we have an exact match with the input
- if util.StringInSlice(input, i.Names()) {
- return input, nil
- }
- // next check if we are missing the tag
- dcImage, err := decompose(input)
- if err != nil {
- return "", err
- }
- imageRegistry, imageName, imageSuspiciousTagValueForSearch := dcImage.suspiciousRefNameTagValuesForSearch()
- for _, repoName := range i.Names() {
- count := 0
- dcRepoName, err := decompose(repoName)
- if err != nil {
- return "", err
- }
- repoNameRegistry, repoNameName, repoNameSuspiciousTagValueForSearch := dcRepoName.suspiciousRefNameTagValuesForSearch()
- if repoNameRegistry == imageRegistry && imageRegistry != "" {
- count++
- }
- if repoNameName == imageName && imageName != "" {
- count++
- } else if splitString(repoNameName) == splitString(imageName) {
- count++
- }
- if repoNameSuspiciousTagValueForSearch == imageSuspiciousTagValueForSearch {
- count++
- }
- results[count] = append(results[count], repoName)
- if count > maxCount {
- maxCount = count
- }
- }
- if maxCount == 0 {
- return "", ErrRepoTagNotFound
- }
- if len(results[maxCount]) > 1 {
- return "", errors.Errorf("user input matched multiple repotags for the image")
- }
- return results[maxCount][0], nil
-}
-
-// splitString splits input string by / and returns the last array item
-func splitString(input string) string {
- split := strings.Split(input, "/")
- return split[len(split)-1]
-}
-
-// IsParent goes through the layers in the store and checks if i.TopLayer is
-// the parent of any other layer in store. Double check that image with that
-// layer exists as well.
-func (i *Image) IsParent(ctx context.Context) (bool, error) {
- children, err := i.getChildren(ctx, false)
- if err != nil {
- if errors.Cause(err) == ErrImageIsBareList {
- return false, nil
- }
- return false, err
- }
- return len(children) > 0, nil
-}
-
-// historiesMatch returns the number of entries in the histories which have the
-// same contents
-func historiesMatch(a, b []ociv1.History) int {
- i := 0
- for i < len(a) && i < len(b) {
- if a[i].Created != nil && b[i].Created == nil {
- return i
- }
- if a[i].Created == nil && b[i].Created != nil {
- return i
- }
- if a[i].Created != nil && b[i].Created != nil {
- if !a[i].Created.Equal(*(b[i].Created)) {
- return i
- }
- }
- if a[i].CreatedBy != b[i].CreatedBy {
- return i
- }
- if a[i].Author != b[i].Author {
- return i
- }
- if a[i].Comment != b[i].Comment {
- return i
- }
- if a[i].EmptyLayer != b[i].EmptyLayer {
- return i
- }
- i++
- }
- return i
-}
-
-// areParentAndChild checks diff ID and history in the two images and return
-// true if the second should be considered to be directly based on the first
-func areParentAndChild(parent, child *ociv1.Image) bool {
- // the child and candidate parent should share all of the
- // candidate parent's diff IDs, which together would have
- // controlled which layers were used
-
- // Both, child and parent, may be nil when the storage is left in an
- // incoherent state. Issue #7444 describes such a case when a build
- // has been killed.
- if child == nil || parent == nil {
- return false
- }
-
- if len(parent.RootFS.DiffIDs) > len(child.RootFS.DiffIDs) {
- return false
- }
- childUsesCandidateDiffs := true
- for i := range parent.RootFS.DiffIDs {
- if child.RootFS.DiffIDs[i] != parent.RootFS.DiffIDs[i] {
- childUsesCandidateDiffs = false
- break
- }
- }
- if !childUsesCandidateDiffs {
- return false
- }
- // the child should have the same history as the parent, plus
- // one more entry
- if len(parent.History)+1 != len(child.History) {
- return false
- }
- if historiesMatch(parent.History, child.History) != len(parent.History) {
- return false
- }
- return true
-}
-
-// GetParent returns the image ID of the parent. Return nil if a parent is not found.
-func (i *Image) GetParent(ctx context.Context) (*Image, error) {
- tree, err := i.imageruntime.layerTree()
- if err != nil {
- return nil, err
- }
- return tree.parent(ctx, i)
-}
-
-// ParentID returns the image ID of the parent. Return empty string if a parent is not found.
-func (i *Image) ParentID(ctx context.Context) (string, error) {
- parent, err := i.GetParent(ctx)
- if err == nil && parent != nil {
- return parent.ID(), nil
- }
- return "", err
-}
-
-// GetChildren returns a list of the imageIDs that depend on the image
-func (i *Image) GetChildren(ctx context.Context) ([]string, error) {
- children, err := i.getChildren(ctx, true)
- if err != nil {
- if errors.Cause(err) == ErrImageIsBareList {
- return nil, nil
- }
- return nil, err
- }
- return children, nil
-}
-
-// getChildren returns a list of imageIDs that depend on the image. If all is
-// false, only the first child image is returned.
-func (i *Image) getChildren(ctx context.Context, all bool) ([]string, error) {
- tree, err := i.imageruntime.layerTree()
- if err != nil {
- return nil, err
- }
-
- return tree.children(ctx, i, all)
-}
-
-// InputIsID returns a bool if the user input for an image
-// is the image's partial or full id
-func (i *Image) InputIsID() bool {
- return strings.HasPrefix(i.ID(), i.InputName)
-}
-
-// Containers a list of container IDs associated with the image
-func (i *Image) Containers() ([]string, error) {
- containers, err := i.imageruntime.store.Containers()
- if err != nil {
- return nil, err
- }
- var imageContainers []string
- for _, c := range containers {
- if c.ImageID == i.ID() {
- imageContainers = append(imageContainers, c.ID)
- }
- }
- return imageContainers, err
-}
-
-// Comment returns the Comment for an image depending on its ManifestType
-func (i *Image) Comment(ctx context.Context, manifestType string) (string, error) {
- if manifestType == manifest.DockerV2Schema2MediaType {
- imgRef, err := i.toImageRef(ctx)
- if err != nil {
- return "", errors.Wrapf(err, "unable to create image reference from image")
- }
- blob, err := imgRef.ConfigBlob(ctx)
- if err != nil {
- return "", errors.Wrapf(err, "unable to get config blob from image")
- }
- b := manifest.Schema2Image{}
- if err := json.Unmarshal(blob, &b); err != nil {
- return "", err
- }
- return b.Comment, nil
- }
- ociv1Img, err := i.ociv1Image(ctx)
- if err != nil {
- if errors.Cause(err) == ErrImageIsBareList {
- return "", nil
- }
- return "", err
- }
- if len(ociv1Img.History) > 0 {
- return ociv1Img.History[0].Comment, nil
- }
- return "", nil
-}
-
-// Save writes a container image to the filesystem
-func (i *Image) Save(ctx context.Context, source, format, output string, moreTags []string, quiet, compress, removeSignatures bool) error {
- var (
- writer io.Writer
- destRef types.ImageReference
- manifestType string
- err error
- )
-
- if quiet {
- writer = os.Stderr
- }
- switch format {
- case "oci-archive":
- destImageName := imageNameForSaveDestination(i, source)
- destRef, err = ociarchive.NewReference(output, destImageName) // destImageName may be ""
- if err != nil {
- return errors.Wrapf(err, "error getting OCI archive ImageReference for (%q, %q)", output, destImageName)
- }
- case "oci-dir":
- destImageName := imageNameForSaveDestination(i, source)
- destRef, err = layout.NewReference(output, destImageName) // destImageName may be ""
- if err != nil {
- return errors.Wrapf(err, "error getting the OCI directory ImageReference for (%q, %q)", output, destImageName)
- }
- manifestType = ociv1.MediaTypeImageManifest
- case "docker-dir":
- destRef, err = directory.NewReference(output)
- if err != nil {
- return errors.Wrapf(err, "error getting directory ImageReference for %q", output)
- }
- manifestType = manifest.DockerV2Schema2MediaType
- case "docker-archive", "":
- destImageName := imageNameForSaveDestination(i, source)
- ref, err := dockerArchiveDstReference(destImageName)
- if err != nil {
- return err
- }
- destRef, err = dockerarchive.NewReference(output, ref)
- if err != nil {
- return errors.Wrapf(err, "error getting Docker archive ImageReference for %s:%v", output, ref)
- }
- default:
- return errors.Errorf("unknown format option %q", format)
- }
- // supports saving multiple tags to the same tar archive
- var additionaltags []reference.NamedTagged
- if len(moreTags) > 0 {
- additionaltags, err = GetAdditionalTags(moreTags)
- if err != nil {
- return err
- }
- }
- if err := i.PushImageToReference(ctx, destRef, manifestType, "", "", "", writer, compress, SigningOptions{RemoveSignatures: removeSignatures}, &DockerRegistryOptions{}, additionaltags, nil); err != nil {
- return errors.Wrapf(err, "unable to save %q", source)
- }
- i.newImageEvent(events.Save)
- return nil
-}
-
-// dockerArchiveDestReference returns a NamedTagged reference for a tagged image and nil for untagged image.
-func dockerArchiveDstReference(normalizedInput string) (reference.NamedTagged, error) {
- if normalizedInput == "" {
- return nil, nil
- }
- ref, err := reference.ParseNormalizedNamed(normalizedInput)
- if err != nil {
- return nil, errors.Wrapf(err, "docker-archive parsing reference %s", normalizedInput)
- }
- ref = reference.TagNameOnly(ref)
- namedTagged, isTagged := ref.(reference.NamedTagged)
- if !isTagged {
- namedTagged = nil
- }
- return namedTagged, nil
-}
-
-// GetConfigBlob returns a schema2image. If the image is not a schema2, then
-// it will return an error
-func (i *Image) GetConfigBlob(ctx context.Context) (*manifest.Schema2Image, error) {
- imageRef, err := i.toImageRef(ctx)
- if err != nil {
- return nil, err
- }
- b, err := imageRef.ConfigBlob(ctx)
- if err != nil {
- return nil, errors.Wrapf(err, "unable to get config blob for %s", i.ID())
- }
- blob := manifest.Schema2Image{}
- if err := json.Unmarshal(b, &blob); err != nil {
- return nil, errors.Wrapf(err, "unable to parse image blob for %s", i.ID())
- }
- return &blob, nil
-}
-
-// GetHealthCheck returns a HealthConfig for an image. This function only works with
-// schema2 images.
-func (i *Image) GetHealthCheck(ctx context.Context) (*manifest.Schema2HealthConfig, error) {
- configBlob, err := i.GetConfigBlob(ctx)
- if err != nil {
- return nil, err
- }
- return configBlob.ContainerConfig.Healthcheck, nil
-}
-
-// newImageEvent creates a new event based on an image
-func (ir *Runtime) newImageEvent(status events.Status, name string) {
- e := events.NewEvent(status)
- e.Type = events.Image
- e.Name = name
- if err := ir.Eventer.Write(e); err != nil {
- logrus.Infof("unable to write event to %s", ir.EventsLogFilePath)
- }
-}
-
-// newImageEvent creates a new event based on an image
-func (i *Image) newImageEvent(status events.Status) {
- e := events.NewEvent(status)
- e.ID = i.ID()
- e.Type = events.Image
- if len(i.Names()) > 0 {
- e.Name = i.Names()[0]
- }
- if err := i.imageruntime.Eventer.Write(e); err != nil {
- logrus.Infof("unable to write event to %s", i.imageruntime.EventsLogFilePath)
- }
-}
-
-// Mount mounts a image's filesystem on the host
-// The path where the image has been mounted is returned
-func (i *Image) Mount(options []string, mountLabel string) (string, error) {
- defer i.newImageEvent(events.Mount)
- return i.mount(options, mountLabel)
-}
-
-// Unmount unmounts a image's filesystem on the host
-func (i *Image) Unmount(force bool) error {
- defer i.newImageEvent(events.Unmount)
- return i.unmount(force)
-}
-
-// Mounted returns whether the image is mounted and the path it is mounted
-// at (if it is mounted).
-// If the image is not mounted, no error is returned, and the mountpoint
-// will be set to "".
-func (i *Image) Mounted() (bool, string, error) {
- mountedTimes, err := i.imageruntime.store.Mounted(i.TopLayer())
- if err != nil {
- return false, "", err
- }
-
- if mountedTimes > 0 {
- layer, err := i.imageruntime.store.Layer(i.TopLayer())
- if err != nil {
- return false, "", err
- }
- return true, layer.MountPoint, nil
- }
-
- return false, "", nil
-}
-
-// mount mounts the container's root filesystem
-func (i *Image) mount(options []string, mountLabel string) (string, error) {
- mountPoint, err := i.imageruntime.store.MountImage(i.ID(), options, mountLabel)
- if err != nil {
- return "", errors.Wrapf(err, "error mounting storage for image %s", i.ID())
- }
- mountPoint, err = filepath.EvalSymlinks(mountPoint)
- if err != nil {
- return "", errors.Wrapf(err, "error resolving storage path for image %s", i.ID())
- }
- return mountPoint, nil
-}
-
-// unmount unmounts the image's root filesystem
-func (i *Image) unmount(force bool) error {
- // Also unmount storage
- if _, err := i.imageruntime.store.UnmountImage(i.ID(), force); err != nil {
- return errors.Wrapf(err, "error unmounting image %s root filesystem", i.ID())
- }
-
- return nil
-}
-
-// LayerInfo keeps information of single layer
-type LayerInfo struct {
- // Layer ID
- ID string
- // Parent ID of current layer.
- ParentID string
- // ChildID of current layer.
- // there can be multiple children in case of fork
- ChildID []string
- // RepoTag will have image repo names, if layer is top layer of image
- RepoTags []string
- // Size stores Uncompressed size of layer.
- Size int64
-}
-
-// GetLayersMapWithImageInfo returns map of image-layers, with associated information like RepoTags, parent and list of child layers.
-func GetLayersMapWithImageInfo(imageruntime *Runtime) (map[string]*LayerInfo, error) {
- // TODO: evaluate if we can reuse `layerTree` here.
-
- // Memory allocated to store map of layers with key LayerID.
- // Map will build dependency chain with ParentID and ChildID(s)
- layerInfoMap := make(map[string]*LayerInfo)
-
- // scan all layers & fill size and parent id for each layer in layerInfoMap
- layers, err := imageruntime.store.Layers()
- if err != nil {
- return nil, err
- }
- for _, layer := range layers {
- _, ok := layerInfoMap[layer.ID]
- if !ok {
- layerInfoMap[layer.ID] = &LayerInfo{
- ID: layer.ID,
- Size: layer.UncompressedSize,
- ParentID: layer.Parent,
- }
- } else {
- return nil, fmt.Errorf("detected multiple layers with the same ID %q", layer.ID)
- }
- }
-
- // scan all layers & add all childid's for each layers to layerInfo
- for _, layer := range layers {
- _, ok := layerInfoMap[layer.ID]
- if ok {
- if layer.Parent != "" {
- layerInfoMap[layer.Parent].ChildID = append(layerInfoMap[layer.Parent].ChildID, layer.ID)
- }
- } else {
- return nil, fmt.Errorf("lookup error: layer-id %s, not found", layer.ID)
- }
- }
-
- // Add the Repo Tags to Top layer of each image.
- imgs, err := imageruntime.store.Images()
- if err != nil {
- return nil, err
- }
- layerInfoMap[""] = &LayerInfo{}
- for _, img := range imgs {
- e, ok := layerInfoMap[img.TopLayer]
- if !ok {
- return nil, fmt.Errorf("top-layer for image %s not found local store", img.ID)
- }
- e.RepoTags = append(e.RepoTags, img.Names...)
- }
- return layerInfoMap, nil
-}
-
-// BuildImageHierarchyMap stores hierarchy of images such that all parent layers using which image is built are stored in imageInfo
-// Layers are added such that (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End)
-func BuildImageHierarchyMap(imageInfo *InfoImage, layerMap map[string]*LayerInfo, layerID string) error {
- if layerID == "" {
- return nil
- }
- ll, ok := layerMap[layerID]
- if !ok {
- return fmt.Errorf("lookup error: layerid %s not found", layerID)
- }
- if err := BuildImageHierarchyMap(imageInfo, layerMap, ll.ParentID); err != nil {
- return err
- }
-
- imageInfo.Layers = append(imageInfo.Layers, *ll)
- return nil
-}
diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go
deleted file mode 100644
index 2b42d6394..000000000
--- a/libpod/image/image_test.go
+++ /dev/null
@@ -1,318 +0,0 @@
-package image
-
-import (
- "context"
- "fmt"
- "io/ioutil"
- "os"
- "testing"
-
- "github.com/containers/podman/v3/libpod/events"
- "github.com/containers/podman/v3/pkg/util"
- podmanVersion "github.com/containers/podman/v3/version"
- "github.com/containers/storage"
- "github.com/containers/storage/pkg/reexec"
- "github.com/opencontainers/go-digest"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-var (
- bbNames = []string{"docker.io/library/busybox:latest", "docker.io/library/busybox", "docker.io/busybox:latest", "docker.io/busybox", "busybox:latest", "busybox"}
- bbGlibcNames = []string{"docker.io/library/busybox:glibc", "docker.io/busybox:glibc", "busybox:glibc"}
-)
-
-type localImageTest struct {
- fqname, taggedName string
- img *Image
- names []string
-}
-
-// make a temporary directory for the runtime
-func mkWorkDir() (string, error) {
- return ioutil.TempDir("", "podman-test")
-}
-
-// shutdown the runtime and clean behind it
-func cleanup(workdir string, ir *Runtime) {
- if err := ir.Shutdown(false); err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
- err := os.RemoveAll(workdir)
- if err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
-}
-
-func makeLocalMatrix(b, bg *Image) []localImageTest {
- var l []localImageTest
- // busybox
- busybox := localImageTest{
- fqname: "docker.io/library/busybox:latest",
- taggedName: "bb:latest",
- }
- busybox.img = b
- busybox.names = b.Names()
- busybox.names = append(busybox.names, []string{"bb:latest", "bb", b.ID(), b.ID()[0:7], fmt.Sprintf("busybox@%s", b.Digest())}...)
-
- // busybox-glibc
- busyboxGlibc := localImageTest{
- fqname: "docker.io/library/busybox:glibc",
- taggedName: "bb:glibc",
- }
-
- busyboxGlibc.img = bg
- busyboxGlibc.names = bbGlibcNames
-
- l = append(l, busybox, busyboxGlibc)
- return l
-}
-
-func TestMain(m *testing.M) {
- if reexec.Init() {
- return
- }
- os.Exit(m.Run())
-}
-
-// TestImage_NewFromLocal tests finding the image locally by various names,
-// tags, and aliases
-func TestImage_NewFromLocal(t *testing.T) {
- if os.Geteuid() != 0 { // containers/storage requires root access
- t.Skipf("Test not running as root")
- }
-
- workdir, err := mkWorkDir()
- assert.NoError(t, err)
- so := storage.StoreOptions{
- RunRoot: workdir,
- GraphRoot: workdir,
- }
- writer := os.Stdout
-
- // Need images to be present for this test
- ir, err := NewImageRuntimeFromOptions(so)
- assert.NoError(t, err)
- defer cleanup(workdir, ir)
-
- ir.Eventer = events.NewNullEventer()
- bb, err := ir.New(context.Background(), "docker.io/library/busybox:latest", "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing, nil)
- assert.NoError(t, err)
- bbglibc, err := ir.New(context.Background(), "docker.io/library/busybox:glibc", "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing, nil)
- assert.NoError(t, err)
-
- tm := makeLocalMatrix(bb, bbglibc)
- for _, image := range tm {
- // tag our images
- err = image.img.TagImage(image.taggedName)
- assert.NoError(t, err)
- for _, name := range image.names {
- newImage, err := ir.NewFromLocal(name)
- require.NoError(t, err)
- assert.Equal(t, newImage.ID(), image.img.ID())
- }
- }
-}
-
-// TestImage_New tests pulling the image by various names, tags, and from
-// different registries
-func TestImage_New(t *testing.T) {
- if os.Geteuid() != 0 { // containers/storage requires root access
- t.Skipf("Test not running as root")
- }
-
- var names []string
- workdir, err := mkWorkDir()
- assert.NoError(t, err)
- so := storage.StoreOptions{
- RunRoot: workdir,
- GraphRoot: workdir,
- }
- ir, err := NewImageRuntimeFromOptions(so)
- assert.NoError(t, err)
- defer cleanup(workdir, ir)
-
- ir.Eventer = events.NewNullEventer()
- // Build the list of pull names
- names = append(names, bbNames...)
- writer := os.Stdout
-
- opts := DockerRegistryOptions{
- RegistriesConfPath: "testdata/registries.conf",
- }
- // Iterate over the names and delete the image
- // after the pull
- for _, img := range names {
- newImage, err := ir.New(context.Background(), img, "", "", writer, &opts, SigningOptions{}, nil, util.PullImageMissing, nil)
- require.NoError(t, err, img)
- assert.NotEqual(t, newImage.ID(), "")
- err = newImage.Remove(context.Background(), false)
- assert.NoError(t, err)
- }
-}
-
-// TestImage_MatchRepoTag tests the various inputs we need to match
-// against an image's reponames
-func TestImage_MatchRepoTag(t *testing.T) {
- if os.Geteuid() != 0 { // containers/storage requires root access
- t.Skipf("Test not running as root")
- }
-
- //Set up
- workdir, err := mkWorkDir()
- assert.NoError(t, err)
- so := storage.StoreOptions{
- RunRoot: workdir,
- GraphRoot: workdir,
- }
- ir, err := NewImageRuntimeFromOptions(so)
- require.NoError(t, err)
- defer cleanup(workdir, ir)
-
- opts := DockerRegistryOptions{
- RegistriesConfPath: "testdata/registries.conf",
- }
- ir.Eventer = events.NewNullEventer()
- newImage, err := ir.New(context.Background(), "busybox", "", "", os.Stdout, &opts, SigningOptions{}, nil, util.PullImageMissing, nil)
- require.NoError(t, err)
- err = newImage.TagImage("foo:latest")
- require.NoError(t, err)
- err = newImage.TagImage("foo:bar")
- require.NoError(t, err)
-
- // Tests start here.
- for _, name := range bbNames {
- repoTag, err := newImage.MatchRepoTag(name)
- assert.NoError(t, err)
- assert.Equal(t, "docker.io/library/busybox:latest", repoTag)
- }
-
- // Test against tagged images of busybox
-
- // foo should resolve to foo:latest
- repoTag, err := newImage.MatchRepoTag("foo")
- require.NoError(t, err)
- assert.Equal(t, "localhost/foo:latest", repoTag)
-
- // foo:bar should resolve to foo:bar
- repoTag, err = newImage.MatchRepoTag("foo:bar")
- require.NoError(t, err)
- assert.Equal(t, "localhost/foo:bar", repoTag)
-}
-
-// TestImage_RepoDigests tests RepoDigest generation.
-func TestImage_RepoDigests(t *testing.T) {
- dgst, err := digest.Parse("sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc")
- require.NoError(t, err)
-
- for _, tt := range []struct {
- name string
- names []string
- expected []string
- }{
- {
- name: "empty",
- names: []string{},
- expected: nil,
- },
- {
- name: "tagged",
- names: []string{"docker.io/library/busybox:latest"},
- expected: []string{"docker.io/library/busybox@sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"},
- },
- {
- name: "digest",
- names: []string{"docker.io/library/busybox@sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"},
- expected: []string{"docker.io/library/busybox@sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"},
- },
- } {
- test := tt
- t.Run(test.name, func(t *testing.T) {
- image := &Image{
- image: &storage.Image{
- Names: test.names,
- Digest: dgst,
- },
- }
- actual, err := image.RepoDigests()
- require.NoError(t, err)
- assert.Equal(t, test.expected, actual)
-
- image = &Image{
- image: &storage.Image{
- Names: test.names,
- Digests: []digest.Digest{dgst},
- },
- }
- actual, err = image.RepoDigests()
- require.NoError(t, err)
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
-// Test_splitString tests the splitString function in image that
-// takes input and splits on / and returns the last array item
-func Test_splitString(t *testing.T) {
- assert.Equal(t, splitString("foo/bar"), "bar")
- assert.Equal(t, splitString("a/foo/bar"), "bar")
- assert.Equal(t, splitString("bar"), "bar")
-}
-
-// Test_stripSha256 tests test the stripSha256 function which removes
-// the prefix "sha256:" from a string if it is present
-func Test_stripSha256(t *testing.T) {
- assert.Equal(t, stripSha256(""), "")
- assert.Equal(t, stripSha256("test1"), "test1")
- assert.Equal(t, stripSha256("sha256:9110ae7f579f35ee0c3938696f23fe0f5fbe641738ea52eb83c2df7e9995fa17"), "9110ae7f579f35ee0c3938696f23fe0f5fbe641738ea52eb83c2df7e9995fa17")
- assert.Equal(t, stripSha256("sha256:9110ae7f"), "9110ae7f")
- assert.Equal(t, stripSha256("sha256:"), "sha256:")
- assert.Equal(t, stripSha256("sha256:a"), "a")
-}
-
-func TestNormalizedTag(t *testing.T) {
- const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
-
- for _, c := range []struct{ input, expected string }{
- {"#", ""}, // Clearly invalid
- {"example.com/busybox", "example.com/busybox:latest"}, // Qualified name-only
- {"example.com/busybox:notlatest", "example.com/busybox:notlatest"}, // Qualified name:tag
- {"example.com/busybox" + digestSuffix, "example.com/busybox" + digestSuffix}, // Qualified name@digest; FIXME? Should we allow tagging with a digest at all?
- {"example.com/busybox:notlatest" + digestSuffix, "example.com/busybox:notlatest" + digestSuffix}, // Qualified name:tag@digest
- {"busybox:latest", "localhost/busybox:latest"}, // Unqualified name-only
- {"ns/busybox:latest", "localhost/ns/busybox:latest"}, // Unqualified with a dot-less namespace
- {"docker.io/busybox:latest", "docker.io/library/busybox:latest"}, // docker.io without /library/
- } {
- res, err := NormalizedTag(c.input)
- if c.expected == "" {
- assert.Error(t, err, c.input)
- } else {
- assert.NoError(t, err, c.input)
- assert.Equal(t, c.expected, res.String())
- }
- }
-}
-
-func TestGetSystemContext(t *testing.T) {
- sc := GetSystemContext("", "", false)
- assert.Equal(t, sc.SignaturePolicyPath, "")
- assert.Equal(t, sc.AuthFilePath, "")
- assert.Equal(t, sc.DirForceCompress, false)
- assert.Equal(t, sc.DockerRegistryUserAgent, fmt.Sprintf("libpod/%s", podmanVersion.Version))
- assert.Equal(t, sc.BigFilesTemporaryDir, "/var/tmp")
-
- oldtmpdir := os.Getenv("TMPDIR")
- os.Setenv("TMPDIR", "/mnt")
- sc = GetSystemContext("/tmp/foo", "/tmp/bar", true)
- assert.Equal(t, sc.SignaturePolicyPath, "/tmp/foo")
- assert.Equal(t, sc.AuthFilePath, "/tmp/bar")
- assert.Equal(t, sc.DirForceCompress, true)
- assert.Equal(t, sc.BigFilesTemporaryDir, "/mnt")
- if oldtmpdir != "" {
- os.Setenv("TMPDIR", oldtmpdir)
- } else {
- os.Unsetenv("TMPDIR")
- }
-}
diff --git a/libpod/image/layer_tree.go b/libpod/image/layer_tree.go
deleted file mode 100644
index aa3084449..000000000
--- a/libpod/image/layer_tree.go
+++ /dev/null
@@ -1,239 +0,0 @@
-package image
-
-import (
- "context"
-
- ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
- "github.com/sirupsen/logrus"
-)
-
-// layerTree is an internal representation of local layers.
-type layerTree struct {
- // nodes is the actual layer tree with layer IDs being keys.
- nodes map[string]*layerNode
- // ociCache is a cache for Image.ID -> OCI Image. Translations are done
- // on-demand.
- ociCache map[string]*ociv1.Image
-}
-
-// node returns a layerNode for the specified layerID.
-func (t *layerTree) node(layerID string) *layerNode {
- node, exists := t.nodes[layerID]
- if !exists {
- node = &layerNode{}
- t.nodes[layerID] = node
- }
- return node
-}
-
-// toOCI returns an OCI image for the specified image.
-func (t *layerTree) toOCI(ctx context.Context, i *Image) (*ociv1.Image, error) {
- var err error
- oci, exists := t.ociCache[i.ID()]
- if !exists {
- oci, err = i.ociv1Image(ctx)
- if err == nil {
- t.ociCache[i.ID()] = oci
- }
- }
- return oci, err
-}
-
-// layerNode is a node in a layerTree. It's ID is the key in a layerTree.
-type layerNode struct {
- children []*layerNode
- images []*Image
- parent *layerNode
-}
-
-// layerTree extracts a layerTree from the layers in the local storage and
-// relates them to the specified images.
-func (ir *Runtime) layerTree() (*layerTree, error) {
- layers, err := ir.store.Layers()
- if err != nil {
- return nil, err
- }
-
- images, err := ir.GetImages()
- if err != nil {
- return nil, err
- }
-
- tree := layerTree{
- nodes: make(map[string]*layerNode),
- ociCache: make(map[string]*ociv1.Image),
- }
-
- // First build a tree purely based on layer information.
- for _, layer := range layers {
- node := tree.node(layer.ID)
- if layer.Parent == "" {
- continue
- }
- parent := tree.node(layer.Parent)
- node.parent = parent
- parent.children = append(parent.children, node)
- }
-
- // Now assign the images to each (top) layer.
- for i := range images {
- img := images[i] // do not leak loop variable outside the scope
- topLayer := img.TopLayer()
- if topLayer == "" {
- continue
- }
- node, exists := tree.nodes[topLayer]
- if !exists {
- // Note: erroring out in this case has turned out having been a
- // mistake. Users may not be able to recover, so we're now
- // throwing a warning to guide them to resolve the issue and
- // turn the errors non-fatal.
- logrus.Warnf("Top layer %s of image %s not found in layer tree. The storage may be corrupted, consider running `podman system reset`.", topLayer, img.ID())
- continue
- }
- node.images = append(node.images, img)
- }
-
- return &tree, nil
-}
-
-// children returns the image IDs of children . Child images are images
-// with either the same top layer as parent or parent being the true parent
-// layer. Furthermore, the history of the parent and child images must match
-// with the parent having one history item less.
-// If all is true, all images are returned. Otherwise, the first image is
-// returned.
-func (t *layerTree) children(ctx context.Context, parent *Image, all bool) ([]string, error) {
- if parent.TopLayer() == "" {
- return nil, nil
- }
-
- var children []string
-
- parentNode, exists := t.nodes[parent.TopLayer()]
- if !exists {
- // Note: erroring out in this case has turned out having been a
- // mistake. Users may not be able to recover, so we're now
- // throwing a warning to guide them to resolve the issue and
- // turn the errors non-fatal.
- logrus.Warnf("Layer %s not found in layer. The storage may be corrupted, consider running `podman system reset`.", parent.TopLayer())
- return children, nil
- }
-
- parentID := parent.ID()
- parentOCI, err := t.toOCI(ctx, parent)
- if err != nil {
- return nil, err
- }
-
- // checkParent returns true if child and parent are in such a relation.
- checkParent := func(child *Image) (bool, error) {
- if parentID == child.ID() {
- return false, nil
- }
- childOCI, err := t.toOCI(ctx, child)
- if err != nil {
- return false, err
- }
- // History check.
- return areParentAndChild(parentOCI, childOCI), nil
- }
-
- // addChildrenFrom adds child images of parent to children. Returns
- // true if any image is a child of parent.
- addChildrenFromNode := func(node *layerNode) (bool, error) {
- foundChildren := false
- for _, childImage := range node.images {
- isChild, err := checkParent(childImage)
- if err != nil {
- return foundChildren, err
- }
- if isChild {
- foundChildren = true
- children = append(children, childImage.ID())
- if all {
- return foundChildren, nil
- }
- }
- }
- return foundChildren, nil
- }
-
- // First check images where parent's top layer is also the parent
- // layer.
- for _, childNode := range parentNode.children {
- found, err := addChildrenFromNode(childNode)
- if err != nil {
- return nil, err
- }
- if found && all {
- return children, nil
- }
- }
-
- // Now check images with the same top layer.
- if _, err := addChildrenFromNode(parentNode); err != nil {
- return nil, err
- }
-
- return children, nil
-}
-
-// parent returns the parent image or nil if no parent image could be found.
-func (t *layerTree) parent(ctx context.Context, child *Image) (*Image, error) {
- if child.TopLayer() == "" {
- return nil, nil
- }
-
- node, exists := t.nodes[child.TopLayer()]
- if !exists {
- // Note: erroring out in this case has turned out having been a
- // mistake. Users may not be able to recover, so we're now
- // throwing a warning to guide them to resolve the issue and
- // turn the errors non-fatal.
- logrus.Warnf("Layer %s not found in layer. The storage may be corrupted, consider running `podman system reset`.", child.TopLayer())
- return nil, nil
- }
-
- childOCI, err := t.toOCI(ctx, child)
- if err != nil {
- return nil, err
- }
-
- // Check images from the parent node (i.e., parent layer) and images
- // with the same layer (i.e., same top layer).
- childID := child.ID()
- images := node.images
- if node.parent != nil {
- images = append(images, node.parent.images...)
- }
- for _, parent := range images {
- if parent.ID() == childID {
- continue
- }
- parentOCI, err := t.toOCI(ctx, parent)
- if err != nil {
- return nil, err
- }
- // History check.
- if areParentAndChild(parentOCI, childOCI) {
- return parent, nil
- }
- }
-
- return nil, nil
-}
-
-// hasChildrenAndParent returns true if the specified image has children and a
-// parent.
-func (t *layerTree) hasChildrenAndParent(ctx context.Context, i *Image) (bool, error) {
- children, err := t.children(ctx, i, false)
- if err != nil {
- return false, err
- }
- if len(children) == 0 {
- return false, nil
- }
- parent, err := t.parent(ctx, i)
- return parent != nil, err
-}
diff --git a/libpod/image/manifests.go b/libpod/image/manifests.go
deleted file mode 100644
index 1ae3693c9..000000000
--- a/libpod/image/manifests.go
+++ /dev/null
@@ -1,209 +0,0 @@
-package image
-
-import (
- "context"
- "fmt"
-
- "github.com/containers/buildah/manifests"
- "github.com/containers/image/v5/docker"
- "github.com/containers/image/v5/manifest"
- "github.com/containers/image/v5/transports/alltransports"
- "github.com/containers/image/v5/types"
- "github.com/opencontainers/go-digest"
-)
-
-// Options for adding a manifest
-// swagger:model ManifestAddOpts
-type ManifestAddOpts struct {
- All bool `json:"all"`
- Annotation map[string]string `json:"annotation"`
- Arch string `json:"arch"`
- Features []string `json:"features"`
- Images []string `json:"images"`
- OS string `json:"os"`
- OSVersion string `json:"os_version"`
- Variant string `json:"variant"`
-}
-
-// ManifestAnnotateOptions defines the options for
-// manifest annotate
-type ManifestAnnotateOpts struct {
- Annotation map[string]string `json:"annotation"`
- Arch string `json:"arch"`
- Features []string `json:"features"`
- OS string `json:"os"`
- OSFeatures []string `json:"os_feature"`
- OSVersion string `json:"os_version"`
- Variant string `json:"variant"`
-}
-
-// InspectManifest returns a dockerized version of the manifest list
-func (i *Image) InspectManifest() (*manifest.Schema2List, error) {
- list, err := i.getManifestList()
- if err != nil {
- return nil, err
- }
- return list.Docker(), nil
-}
-
-// ExistsManifest checks if a manifest list exists
-func (i *Image) ExistsManifest() (bool, error) {
- _, err := i.getManifestList()
- if err != nil {
- return false, err
- }
- return true, nil
-}
-
-// RemoveManifest removes the given digest from the manifest list.
-func (i *Image) RemoveManifest(d digest.Digest) (string, error) {
- list, err := i.getManifestList()
- if err != nil {
- return "", err
- }
- if err := list.Remove(d); err != nil {
- return "", err
- }
- return list.SaveToImage(i.imageruntime.store, i.ID(), nil, "")
-}
-
-// getManifestList is a helper to obtain a manifest list
-func (i *Image) getManifestList() (manifests.List, error) {
- _, list, err := manifests.LoadFromImage(i.imageruntime.store, i.ID())
- return list, err
-}
-
-// CreateManifestList creates a new manifest list and can optionally add given images
-// to the list
-func CreateManifestList(rt *Runtime, systemContext types.SystemContext, names []string, imgs []string, all bool) (string, error) {
- list := manifests.Create()
- opts := ManifestAddOpts{Images: names, All: all}
- for _, img := range imgs {
- ref, err := alltransports.ParseImageName(img)
- if err != nil {
- dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name())
- ref, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, img))
- if err != nil {
- return "", err
- }
- }
- list, err = addManifestToList(ref, list, systemContext, opts)
- if err != nil {
- return "", err
- }
- }
- return list.SaveToImage(rt.store, "", names, manifest.DockerV2ListMediaType)
-}
-
-func addManifestToList(ref types.ImageReference, list manifests.List, systemContext types.SystemContext, opts ManifestAddOpts) (manifests.List, error) {
- d, err := list.Add(context.Background(), &systemContext, ref, opts.All)
- if err != nil {
- return nil, err
- }
- if opts.OS != "" {
- if err := list.SetOS(d, opts.OS); err != nil {
- return nil, err
- }
- }
- if len(opts.OSVersion) > 0 {
- if err := list.SetOSVersion(d, opts.OSVersion); err != nil {
- return nil, err
- }
- }
- if len(opts.Features) > 0 {
- if err := list.SetFeatures(d, opts.Features); err != nil {
- return nil, err
- }
- }
- if len(opts.Arch) > 0 {
- if err := list.SetArchitecture(d, opts.Arch); err != nil {
- return nil, err
- }
- }
- if len(opts.Variant) > 0 {
- if err := list.SetVariant(d, opts.Variant); err != nil {
- return nil, err
- }
- }
- if len(opts.Annotation) > 0 {
- if err := list.SetAnnotations(&d, opts.Annotation); err != nil {
- return nil, err
- }
- }
- return list, err
-}
-
-// AddManifest adds a manifest to a given manifest list.
-func (i *Image) AddManifest(systemContext types.SystemContext, opts ManifestAddOpts) (string, error) {
- ref, err := alltransports.ParseImageName(opts.Images[0])
- if err != nil {
- dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name())
- ref, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, opts.Images[0]))
- if err != nil {
- return "", err
- }
- }
- list, err := i.getManifestList()
- if err != nil {
- return "", err
- }
- list, err = addManifestToList(ref, list, systemContext, opts)
- if err != nil {
- return "", err
- }
- return list.SaveToImage(i.imageruntime.store, i.ID(), nil, "")
-}
-
-// PushManifest pushes a manifest to a destination
-func (i *Image) PushManifest(dest types.ImageReference, opts manifests.PushOptions) (digest.Digest, error) {
- list, err := i.getManifestList()
- if err != nil {
- return "", err
- }
- _, d, err := list.Push(context.Background(), dest, opts)
- return d, err
-}
-
-// AnnotateManifest updates an image configuration of a manifest list.
-func (i *Image) AnnotateManifest(systemContext types.SystemContext, d digest.Digest, opts ManifestAnnotateOpts) (string, error) {
- list, err := i.getManifestList()
- if err != nil {
- return "", err
- }
- if len(opts.OS) > 0 {
- if err := list.SetOS(d, opts.OS); err != nil {
- return "", err
- }
- }
- if len(opts.OSVersion) > 0 {
- if err := list.SetOSVersion(d, opts.OSVersion); err != nil {
- return "", err
- }
- }
- if len(opts.Features) > 0 {
- if err := list.SetFeatures(d, opts.Features); err != nil {
- return "", err
- }
- }
- if len(opts.OSFeatures) > 0 {
- if err := list.SetOSFeatures(d, opts.OSFeatures); err != nil {
- return "", err
- }
- }
- if len(opts.Arch) > 0 {
- if err := list.SetArchitecture(d, opts.Arch); err != nil {
- return "", err
- }
- }
- if len(opts.Variant) > 0 {
- if err := list.SetVariant(d, opts.Variant); err != nil {
- return "", err
- }
- }
- if len(opts.Annotation) > 0 {
- if err := list.SetAnnotations(&d, opts.Annotation); err != nil {
- return "", err
- }
- }
- return list.SaveToImage(i.imageruntime.store, i.ID(), nil, "")
-}
diff --git a/libpod/image/parts.go b/libpod/image/parts.go
deleted file mode 100644
index 08421320c..000000000
--- a/libpod/image/parts.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package image
-
-import (
- "strings"
-
- "github.com/containers/image/v5/docker/reference"
- "github.com/pkg/errors"
-)
-
-// imageParts describes the parts of an image's name
-type imageParts struct {
- unnormalizedRef reference.Named // WARNING: Did not go through docker.io[/library] normalization
- hasRegistry bool
-}
-
-// Registries must contain a ":" or a "." or be localhost; this helper exists for users of reference.Parse.
-// For inputs that should use the docker.io[/library] normalization, use reference.ParseNormalizedNamed instead.
-func isRegistry(name string) bool {
- return strings.ContainsAny(name, ".:") || name == "localhost"
-}
-
-// GetImageBaseName uses decompose and string splits to obtain the base
-// name of an image. Doing this here because it beats changing the
-// imageParts struct names to be exported as well.
-func GetImageBaseName(input string) (string, error) {
- decomposedImage, err := decompose(input)
- if err != nil {
- return "", err
- }
- splitImageName := strings.Split(decomposedImage.unnormalizedRef.Name(), "/")
- return splitImageName[len(splitImageName)-1], nil
-}
-
-// decompose breaks an input name into an imageParts description
-func decompose(input string) (imageParts, error) {
- imgRef, err := reference.Parse(input)
- if err != nil {
- return imageParts{}, err
- }
- unnormalizedNamed := imgRef.(reference.Named)
- // ip.unnormalizedRef, because it uses reference.Parse and not reference.ParseNormalizedNamed,
- // does not use the standard heuristics for domains vs. namespaces/repos, so we need to check
- // explicitly.
- hasRegistry := isRegistry(reference.Domain(unnormalizedNamed))
- return imageParts{
- unnormalizedRef: unnormalizedNamed,
- hasRegistry: hasRegistry,
- }, nil
-}
-
-// suspiciousRefNameTagValuesForSearch returns a "tag" value used in a previous implementation.
-// This exists only to preserve existing behavior in heuristic code; it’s dubious that that behavior is correct,
-// especially for the tag value.
-func (ip *imageParts) suspiciousRefNameTagValuesForSearch() (string, string, string) {
- registry := reference.Domain(ip.unnormalizedRef)
- imageName := reference.Path(ip.unnormalizedRef)
- // ip.unnormalizedRef, because it uses reference.Parse and not reference.ParseNormalizedNamed,
- // does not use the standard heuristics for domains vs. namespaces/repos.
- if registry != "" && !isRegistry(registry) {
- imageName = registry + "/" + imageName
- registry = ""
- }
-
- var tag string
- if tagged, isTagged := ip.unnormalizedRef.(reference.NamedTagged); isTagged {
- tag = tagged.Tag()
- } else if _, hasDigest := ip.unnormalizedRef.(reference.Digested); hasDigest {
- tag = "none"
- } else {
- tag = LatestTag
- }
- return registry, imageName, tag
-}
-
-// referenceWithRegistry returns a (normalized) reference.Named composed of ip (with !ip.hasRegistry)
-// qualified with registry.
-func (ip *imageParts) referenceWithRegistry(registry string) (reference.Named, error) {
- if ip.hasRegistry {
- return nil, errors.Errorf("internal error: referenceWithRegistry called on imageParts with a registry (%#v)", *ip)
- }
- // We could build a reference.WithName+WithTag/WithDigest here, but we need to round-trip via a string
- // and a ParseNormalizedNamed anyway to get the right normalization of docker.io/library, so
- // just use a string directly.
- qualified := registry + "/" + ip.unnormalizedRef.String()
- ref, err := reference.ParseNormalizedNamed(qualified)
- if err != nil {
- return nil, errors.Wrapf(err, "error normalizing registry+unqualified reference %#v", qualified)
- }
- return ref, nil
-}
-
-// normalizedReference returns a (normalized) reference for ip (with ip.hasRegistry)
-func (ip *imageParts) normalizedReference() (reference.Named, error) {
- if !ip.hasRegistry {
- return nil, errors.Errorf("internal error: normalizedReference called on imageParts without a registry (%#v)", *ip)
- }
- // We need to round-trip via a string to get the right normalization of docker.io/library
- s := ip.unnormalizedRef.String()
- ref, err := reference.ParseNormalizedNamed(s)
- if err != nil { // Should never happen
- return nil, errors.Wrapf(err, "error normalizing qualified reference %#v", s)
- }
- return ref, nil
-}
diff --git a/libpod/image/parts_test.go b/libpod/image/parts_test.go
deleted file mode 100644
index 726e55e86..000000000
--- a/libpod/image/parts_test.go
+++ /dev/null
@@ -1,123 +0,0 @@
-package image
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestDecompose(t *testing.T) {
- const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
-
- for _, c := range []struct {
- input string
- registry, name, suspiciousTagValueForSearch string
- hasRegistry bool
- }{
- {"#", "", "", "", false}, // Entirely invalid input
- { // Fully qualified docker.io, name-only input
- "docker.io/library/busybox", "docker.io", "library/busybox", "latest", true,
- },
- { // Fully qualified example.com, name-only input
- "example.com/ns/busybox", "example.com", "ns/busybox", "latest", true,
- },
- { // Unqualified single-name input
- "busybox", "", "busybox", "latest", false,
- },
- { // Unqualified namespaced input
- "ns/busybox", "", "ns/busybox", "latest", false,
- },
- { // name:tag
- "example.com/ns/busybox:notlatest", "example.com", "ns/busybox", "notlatest", true,
- },
- { // name@digest
- // FIXME? .suspiciousTagValueForSearch == "none"
- "example.com/ns/busybox" + digestSuffix, "example.com", "ns/busybox", "none", true,
- },
- { // name:tag@digest
- "example.com/ns/busybox:notlatest" + digestSuffix, "example.com", "ns/busybox", "notlatest", true,
- },
- } {
- parts, err := decompose(c.input)
- if c.name == "" {
- assert.Error(t, err, c.input)
- } else {
- assert.NoError(t, err, c.input)
- registry, name, suspiciousTagValueForSearch := parts.suspiciousRefNameTagValuesForSearch()
- assert.Equal(t, c.registry, registry, c.input)
- assert.Equal(t, c.name, name, c.input)
- assert.Equal(t, c.suspiciousTagValueForSearch, suspiciousTagValueForSearch, c.input)
- assert.Equal(t, c.hasRegistry, parts.hasRegistry, c.input)
- }
- }
-}
-
-func TestImagePartsReferenceWithRegistry(t *testing.T) {
- const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
-
- for _, c := range []struct {
- input string
- withDocker, withNonDocker string
- }{
- {"example.com/ns/busybox", "", ""}, // Fully-qualified input is invalid.
- {"busybox", "docker.io/library/busybox", "example.com/busybox"}, // Single-name input
- {"ns/busybox", "docker.io/ns/busybox", "example.com/ns/busybox"}, // Namespaced input
- {"ns/busybox:notlatest", "docker.io/ns/busybox:notlatest", "example.com/ns/busybox:notlatest"}, // name:tag
- {"ns/busybox" + digestSuffix, "docker.io/ns/busybox" + digestSuffix, "example.com/ns/busybox" + digestSuffix}, // name@digest
- { // name:tag@digest
- "ns/busybox:notlatest" + digestSuffix,
- "docker.io/ns/busybox:notlatest" + digestSuffix, "example.com/ns/busybox:notlatest" + digestSuffix,
- },
- } {
- parts, err := decompose(c.input)
- require.NoError(t, err)
- if c.withDocker == "" {
- _, err := parts.referenceWithRegistry("docker.io")
- assert.Error(t, err, c.input)
- _, err = parts.referenceWithRegistry("example.com")
- assert.Error(t, err, c.input)
- } else {
- ref, err := parts.referenceWithRegistry("docker.io")
- require.NoError(t, err, c.input)
- assert.Equal(t, c.withDocker, ref.String())
- ref, err = parts.referenceWithRegistry("example.com")
- require.NoError(t, err, c.input)
- assert.Equal(t, c.withNonDocker, ref.String())
- }
- }
-
- // Invalid registry value
- parts, err := decompose("busybox")
- require.NoError(t, err)
- _, err = parts.referenceWithRegistry("invalid@domain")
- assert.Error(t, err)
-}
-
-func TestImagePartsNormalizedReference(t *testing.T) {
- const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
-
- for _, c := range []struct{ input, expected string }{
- {"busybox", ""}, // Unqualified input is invalid
- {"docker.io/busybox", "docker.io/library/busybox"}, // docker.io single-name
- {"example.com/busybox", "example.com/busybox"}, // example.com single-name
- {"docker.io/ns/busybox", "docker.io/ns/busybox"}, // docker.io namespaced
- {"example.com/ns/busybox", "example.com/ns/busybox"}, // example.com namespaced
- {"example.com/ns/busybox:notlatest", "example.com/ns/busybox:notlatest"}, // name:tag
- {"example.com/ns/busybox" + digestSuffix, "example.com/ns/busybox" + digestSuffix}, // name@digest
- { // name:tag@digest
- "example.com/ns/busybox:notlatest" + digestSuffix, "example.com/ns/busybox:notlatest" + digestSuffix,
- },
- } {
- parts, err := decompose(c.input)
- require.NoError(t, err)
- if c.expected == "" {
- _, err := parts.normalizedReference()
- assert.Error(t, err, c.input)
- } else {
- ref, err := parts.normalizedReference()
- require.NoError(t, err, c.input)
- assert.Equal(t, c.expected, ref.String())
- }
- }
-}
diff --git a/libpod/image/prune.go b/libpod/image/prune.go
deleted file mode 100644
index e0480d3d1..000000000
--- a/libpod/image/prune.go
+++ /dev/null
@@ -1,164 +0,0 @@
-package image
-
-import (
- "context"
- "strconv"
- "strings"
-
- "github.com/containers/podman/v3/libpod/events"
- "github.com/containers/podman/v3/pkg/domain/entities/reports"
- "github.com/containers/podman/v3/pkg/util"
- "github.com/containers/storage"
- "github.com/pkg/errors"
- "github.com/sirupsen/logrus"
-)
-
-func generatePruneFilterFuncs(filter, filterValue string) (ImageFilter, error) {
- switch filter {
- case "label":
- return func(i *Image) bool {
- labels, err := i.Labels(context.Background())
- if err != nil {
- return false
- }
- return util.MatchLabelFilters([]string{filterValue}, labels)
- }, nil
-
- case "until":
- until, err := util.ComputeUntilTimestamp([]string{filterValue})
- if err != nil {
- return nil, err
- }
- return func(i *Image) bool {
- if !until.IsZero() && i.Created().Before(until) {
- return true
- }
- return false
- }, nil
- case "dangling":
- danglingImages, err := strconv.ParseBool(filterValue)
- if err != nil {
- return nil, errors.Wrapf(err, "invalid filter dangling=%s", filterValue)
- }
- return ImageFilter(DanglingFilter(danglingImages)), nil
- }
- return nil, nil
-}
-
-// GetPruneImages returns a slice of images that have no names/unused
-func (ir *Runtime) GetPruneImages(ctx context.Context, all bool, filterFuncs []ImageFilter) ([]*Image, error) {
- var (
- pruneImages []*Image
- )
-
- allImages, err := ir.GetRWImages()
- if err != nil {
- return nil, err
- }
-
- tree, err := ir.layerTree()
- if err != nil {
- return nil, err
- }
-
- for _, i := range allImages {
- // filter the images based on this.
- for _, filterFunc := range filterFuncs {
- if !filterFunc(i) {
- continue
- }
- }
-
- if all {
- containers, err := i.Containers()
- if err != nil {
- return nil, err
- }
- if len(containers) < 1 {
- pruneImages = append(pruneImages, i)
- continue
- }
- }
-
- // skip the cache (i.e., with parent) and intermediate (i.e.,
- // with children) images
- intermediate, err := tree.hasChildrenAndParent(ctx, i)
- if err != nil {
- return nil, err
- }
- if intermediate {
- continue
- }
-
- if i.Dangling() {
- pruneImages = append(pruneImages, i)
- }
- }
- return pruneImages, nil
-}
-
-// PruneImages prunes dangling and optionally all unused images from the local
-// image store
-func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) ([]*reports.PruneReport, error) {
- preports := make([]*reports.PruneReport, 0)
- filterFuncs := make([]ImageFilter, 0, len(filter))
- for _, f := range filter {
- filterSplit := strings.SplitN(f, "=", 2)
- if len(filterSplit) < 2 {
- return nil, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
- }
-
- generatedFunc, err := generatePruneFilterFuncs(filterSplit[0], filterSplit[1])
- if err != nil {
- return nil, errors.Wrapf(err, "invalid filter")
- }
- filterFuncs = append(filterFuncs, generatedFunc)
- }
-
- prev := 0
- for {
- toPrune, err := ir.GetPruneImages(ctx, all, filterFuncs)
- if err != nil {
- return nil, errors.Wrap(err, "unable to get images to prune")
- }
- numImages := len(toPrune)
- if numImages == 0 || numImages == prev {
- // If there's nothing left to do, return.
- break
- }
- prev = numImages
- for _, img := range toPrune {
- repotags, err := img.RepoTags()
- if err != nil {
- return nil, err
- }
- nameOrID := img.ID()
- s, err := img.Size(ctx)
- imgSize := uint64(0)
- if err != nil {
- logrus.Warnf("Failed to collect image size for: %s, %s", nameOrID, err)
- } else {
- imgSize = *s
- }
- if err := img.Remove(ctx, false); err != nil {
- if errors.Cause(err) == storage.ErrImageUsedByContainer {
- logrus.Warnf("Failed to prune image %s as it is in use: %v.\nA container associated with containers/storage (e.g., Buildah, CRI-O, etc.) maybe associated with this image.\nUsing the rmi command with the --force option will remove the container and image, but may cause failures for other dependent systems.", img.ID(), err)
- continue
- }
- return nil, errors.Wrap(err, "failed to prune image")
- }
- defer img.newImageEvent(events.Prune)
-
- if len(repotags) > 0 {
- nameOrID = repotags[0]
- }
-
- preports = append(preports, &reports.PruneReport{
- Id: nameOrID,
- Err: nil,
- Size: uint64(imgSize),
- })
- }
- }
- return preports, nil
-}
diff --git a/libpod/image/pull.go b/libpod/image/pull.go
deleted file mode 100644
index 6517fbd07..000000000
--- a/libpod/image/pull.go
+++ /dev/null
@@ -1,437 +0,0 @@
-package image
-
-import (
- "context"
- "fmt"
- "io"
- "path/filepath"
- "strings"
- "time"
-
- "github.com/containers/common/pkg/retry"
- cp "github.com/containers/image/v5/copy"
- "github.com/containers/image/v5/directory"
- "github.com/containers/image/v5/docker"
- dockerarchive "github.com/containers/image/v5/docker/archive"
- ociarchive "github.com/containers/image/v5/oci/archive"
- oci "github.com/containers/image/v5/oci/layout"
- "github.com/containers/image/v5/pkg/shortnames"
- is "github.com/containers/image/v5/storage"
- "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/events"
- "github.com/containers/podman/v3/pkg/errorhandling"
- "github.com/containers/podman/v3/pkg/registries"
- "github.com/pkg/errors"
- "github.com/sirupsen/logrus"
-)
-
-var (
- // DockerArchive is the transport we prepend to an image name
- // when saving to docker-archive
- DockerArchive = dockerarchive.Transport.Name()
- // OCIArchive is the transport we prepend to an image name
- // when saving to oci-archive
- OCIArchive = ociarchive.Transport.Name()
- // DirTransport is the transport for pushing and pulling
- // images to and from a directory
- DirTransport = directory.Transport.Name()
- // DockerTransport is the transport for docker registries
- DockerTransport = docker.Transport.Name()
- // OCIDirTransport is the transport for pushing and pulling
- // images to and from a directory containing an OCI image
- OCIDirTransport = oci.Transport.Name()
- // AtomicTransport is the transport for atomic registries
- AtomicTransport = "atomic"
- // DefaultTransport is a prefix that we apply to an image name
- // NOTE: This is a string prefix, not actually a transport name usable for transports.Get();
- // and because syntaxes of image names are transport-dependent, the prefix is not really interchangeable;
- // each user implicitly assumes the appended string is a Docker-like reference.
- DefaultTransport = DockerTransport + "://"
- // DefaultLocalRegistry is the default local registry for local image operations
- // Remote pulls will still use defined registries
- DefaultLocalRegistry = "localhost"
-)
-
-// pullRefPair records a pair of prepared image references to pull.
-type pullRefPair struct {
- image string
- srcRef types.ImageReference
- dstRef types.ImageReference
- resolvedShortname *shortnames.PullCandidate // if set, must be recorded after successful pull
-}
-
-// cleanUpFunc is a function prototype for clean-up functions.
-type cleanUpFunc func() error
-
-// pullGoal represents the prepared image references and decided behavior to be executed by imagePull
-type pullGoal struct {
- refPairs []pullRefPair
- pullAllPairs bool // Pull all refPairs instead of stopping on first success.
- cleanUpFuncs []cleanUpFunc // Mainly used to close long-lived objects (e.g., an archive.Reader)
- shortName string // Set when pulling a short name
- resolved *shortnames.Resolved // Set when pulling a short name
-}
-
-// cleanUp invokes all cleanUpFuncs. Certain resources may not be available
-// anymore. Errors are logged.
-func (p *pullGoal) cleanUp() {
- for _, f := range p.cleanUpFuncs {
- if err := f(); err != nil {
- logrus.Error(err.Error())
- }
- }
-}
-
-// singlePullRefPairGoal returns a no-frills pull goal for the specified reference pair.
-func singlePullRefPairGoal(rp pullRefPair) *pullGoal {
- return &pullGoal{
- refPairs: []pullRefPair{rp},
- pullAllPairs: false, // Does not really make a difference.
- }
-}
-
-func (ir *Runtime) getPullRefPair(srcRef types.ImageReference, destName string) (pullRefPair, error) {
- decomposedDest, err := decompose(destName)
- if err == nil && !decomposedDest.hasRegistry {
- // If the image doesn't have a registry, set it as the default repo
- ref, err := decomposedDest.referenceWithRegistry(DefaultLocalRegistry)
- if err != nil {
- return pullRefPair{}, err
- }
- destName = ref.String()
- }
-
- reference := destName
- if srcRef.DockerReference() != nil {
- reference = srcRef.DockerReference().String()
- }
- destRef, err := is.Transport.ParseStoreReference(ir.store, reference)
- if err != nil {
- return pullRefPair{}, errors.Wrapf(err, "error parsing dest reference name %#v", destName)
- }
- return pullRefPair{
- image: destName,
- srcRef: srcRef,
- dstRef: destRef,
- }, nil
-}
-
-// getSinglePullRefPairGoal calls getPullRefPair with the specified parameters, and returns a single-pair goal for the return value.
-func (ir *Runtime) getSinglePullRefPairGoal(srcRef types.ImageReference, destName string) (*pullGoal, error) {
- rp, err := ir.getPullRefPair(srcRef, destName)
- if err != nil {
- return nil, err
- }
- return singlePullRefPairGoal(rp), nil
-}
-
-// getPullRefPairsFromDockerArchiveReference returns a slice of pullRefPairs
-// for the specified docker reference and the corresponding archive.Reader.
-func (ir *Runtime) getPullRefPairsFromDockerArchiveReference(ctx context.Context, reader *dockerarchive.Reader, ref types.ImageReference, sc *types.SystemContext) ([]pullRefPair, error) {
- destNames, err := reader.ManifestTagsForReference(ref)
- if err != nil {
- return nil, err
- }
-
- if len(destNames) == 0 {
- destName, err := getImageDigest(ctx, ref, sc)
- if err != nil {
- return nil, err
- }
- destNames = append(destNames, destName)
- } else {
- for i := range destNames {
- ref, err := NormalizedTag(destNames[i])
- if err != nil {
- return nil, err
- }
- destNames[i] = ref.String()
- }
- }
-
- refPairs := []pullRefPair{}
- for _, destName := range destNames {
- destRef, err := is.Transport.ParseStoreReference(ir.store, destName)
- if err != nil {
- return nil, errors.Wrapf(err, "error parsing dest reference name %#v", destName)
- }
- pair := pullRefPair{
- image: destName,
- srcRef: ref,
- dstRef: destRef,
- }
- refPairs = append(refPairs, pair)
- }
-
- return refPairs, nil
-}
-
-// pullGoalFromImageReference returns a pull goal for a single ImageReference, depending on the used transport.
-// Note that callers are responsible for invoking (*pullGoal).cleanUp() to clean up possibly open resources.
-func (ir *Runtime) pullGoalFromImageReference(ctx context.Context, srcRef types.ImageReference, imgName string, sc *types.SystemContext) (*pullGoal, error) {
- // supports pulling from docker-archive, oci, and registries
- switch srcRef.Transport().Name() {
- case DockerArchive:
- reader, readerRef, err := dockerarchive.NewReaderForReference(sc, srcRef)
- if err != nil {
- return nil, err
- }
-
- pairs, err := ir.getPullRefPairsFromDockerArchiveReference(ctx, reader, readerRef, sc)
- if err != nil {
- // No need to defer for a single error path.
- if err := reader.Close(); err != nil {
- logrus.Error(err.Error())
- }
- return nil, err
- }
-
- return &pullGoal{
- pullAllPairs: true,
- refPairs: pairs,
- cleanUpFuncs: []cleanUpFunc{reader.Close},
- }, nil
-
- case OCIArchive:
- // retrieve the manifest from index.json to access the image name
- manifest, err := ociarchive.LoadManifestDescriptor(srcRef)
- if err != nil {
- return nil, errors.Wrapf(err, "error loading manifest for %q", srcRef)
- }
- var dest string
- if manifest.Annotations == nil || manifest.Annotations["org.opencontainers.image.ref.name"] == "" {
- // If the input image has no image.ref.name, we need to feed it a dest anyways
- // use the hex of the digest
- dest, err = getImageDigest(ctx, srcRef, sc)
- if err != nil {
- return nil, errors.Wrapf(err, "error getting image digest; image reference not found")
- }
- } else {
- dest = manifest.Annotations["org.opencontainers.image.ref.name"]
- }
- return ir.getSinglePullRefPairGoal(srcRef, dest)
-
- case DirTransport:
- image := toLocalImageName(srcRef.StringWithinTransport())
- return ir.getSinglePullRefPairGoal(srcRef, image)
-
- case OCIDirTransport:
- split := strings.SplitN(srcRef.StringWithinTransport(), ":", 2)
- image := toLocalImageName(split[0])
- return ir.getSinglePullRefPairGoal(srcRef, image)
-
- default:
- return ir.getSinglePullRefPairGoal(srcRef, imgName)
- }
-}
-
-// toLocalImageName converts an image name into a 'localhost/' prefixed one
-func toLocalImageName(imageName string) string {
- return fmt.Sprintf(
- "%s/%s",
- DefaultLocalRegistry,
- strings.TrimLeft(imageName, "/"),
- )
-}
-
-// pullImageFromHeuristicSource pulls an image based on inputName, which is heuristically parsed and may involve configured registries.
-// Use pullImageFromReference if the source is known precisely.
-func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName string, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions, label *string, progress chan types.ProgressProperties) ([]string, error) {
- var goal *pullGoal
- sc := GetSystemContext(signaturePolicyPath, authfile, false)
- if dockerOptions != nil {
- sc.OSChoice = dockerOptions.OSChoice
- sc.ArchitectureChoice = dockerOptions.ArchitectureChoice
- sc.VariantChoice = dockerOptions.VariantChoice
- sc.SystemRegistriesConfPath = dockerOptions.RegistriesConfPath
- }
- if signaturePolicyPath == "" {
- sc.SignaturePolicyPath = ir.SignaturePolicyPath
- }
- sc.BlobInfoCacheDir = filepath.Join(ir.store.GraphRoot(), "cache")
- srcRef, err := alltransports.ParseImageName(inputName)
- if err != nil {
- // We might be pulling with an unqualified image reference in which case
- // we need to make sure that we're not using any other transport.
- srcTransport := alltransports.TransportFromImageName(inputName)
- if srcTransport != nil && srcTransport.Name() != DockerTransport {
- return nil, err
- }
- goal, err = ir.pullGoalFromPossiblyUnqualifiedName(sc, writer, inputName)
- if err != nil {
- return nil, errors.Wrap(err, "error getting default registries to try")
- }
- } else {
- goal, err = ir.pullGoalFromImageReference(ctx, srcRef, inputName, sc)
- if err != nil {
- return nil, errors.Wrapf(err, "error determining pull goal for image %q", inputName)
- }
- }
- defer goal.cleanUp()
- return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, retryOptions, label, progress)
-}
-
-// pullImageFromReference pulls an image from a types.imageReference.
-func (ir *Runtime) pullImageFromReference(ctx context.Context, srcRef types.ImageReference, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions) ([]string, error) {
- sc := GetSystemContext(signaturePolicyPath, authfile, false)
- if dockerOptions != nil {
- sc.OSChoice = dockerOptions.OSChoice
- sc.ArchitectureChoice = dockerOptions.ArchitectureChoice
- sc.VariantChoice = dockerOptions.VariantChoice
- }
- goal, err := ir.pullGoalFromImageReference(ctx, srcRef, transports.ImageName(srcRef), sc)
- if err != nil {
- return nil, errors.Wrapf(err, "error determining pull goal for image %q", transports.ImageName(srcRef))
- }
- defer goal.cleanUp()
- return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, retryOptions, nil, nil)
-}
-
-func cleanErrorMessage(err error) string {
- errMessage := strings.TrimPrefix(errors.Cause(err).Error(), "errors:\n")
- errMessage = strings.Split(errMessage, "\n")[0]
- return fmt.Sprintf(" %s\n", errMessage)
-}
-
-// doPullImage is an internal helper interpreting pullGoal. Almost everyone should call one of the callers of doPullImage instead.
-func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goal pullGoal, writer io.Writer, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions, label *string, progress chan types.ProgressProperties) ([]string, error) {
- policyContext, err := getPolicyContext(sc)
- if err != nil {
- return nil, err
- }
- defer func() {
- if err := policyContext.Destroy(); err != nil {
- logrus.Errorf("failed to destroy policy context: %q", err)
- }
- }()
-
- var systemRegistriesConfPath string
- if dockerOptions != nil && dockerOptions.RegistriesConfPath != "" {
- systemRegistriesConfPath = dockerOptions.RegistriesConfPath
- } else {
- systemRegistriesConfPath = registries.SystemRegistriesConfPath()
- }
-
- var (
- images []string
- pullErrors []error
- )
-
- for _, imageInfo := range goal.refPairs {
- copyOptions := getCopyOptions(sc, writer, dockerOptions, nil, signingOptions, "", nil)
- copyOptions.SourceCtx.SystemRegistriesConfPath = systemRegistriesConfPath // FIXME: Set this more globally. Probably no reason not to have it in every types.SystemContext, and to compute the value just once in one place.
- if progress != nil {
- copyOptions.Progress = progress
- copyOptions.ProgressInterval = time.Second
- }
- // Print the following statement only when pulling from a docker or atomic registry
- if writer != nil && (imageInfo.srcRef.Transport().Name() == DockerTransport || imageInfo.srcRef.Transport().Name() == AtomicTransport) {
- if _, err := io.WriteString(writer, fmt.Sprintf("Trying to pull %s...\n", imageInfo.image)); err != nil {
- return nil, err
- }
- }
- // If the label is not nil, check if the label exists and if not, return err
- if label != nil {
- if err := checkRemoteImageForLabel(ctx, *label, imageInfo, sc); err != nil {
- return nil, err
- }
- }
- imageInfo := imageInfo
- if err = retry.RetryIfNecessary(ctx, func() error {
- _, err = cp.Image(ctx, policyContext, imageInfo.dstRef, imageInfo.srcRef, copyOptions)
- return err
- }, retryOptions); err != nil {
- pullErrors = append(pullErrors, err)
- logrus.Debugf("Error pulling image ref %s: %v", imageInfo.srcRef.StringWithinTransport(), err)
- if writer != nil {
- _, _ = io.WriteString(writer, cleanErrorMessage(err))
- }
- } else {
- if imageInfo.resolvedShortname != nil {
- if err := imageInfo.resolvedShortname.Record(); err != nil {
- logrus.Errorf("Error recording short-name alias %q: %v", imageInfo.resolvedShortname.Value.String(), err)
- }
- }
- if !goal.pullAllPairs {
- ir.newImageEvent(events.Pull, "")
- return []string{imageInfo.image}, nil
- }
- images = append(images, imageInfo.image)
- }
- }
- // If no image was found, we should handle. Lets be nicer to the user
- // and see if we can figure out why.
- if len(images) == 0 {
- if goal.resolved != nil {
- return nil, goal.resolved.FormatPullErrors(pullErrors)
- }
- return nil, errorhandling.JoinErrors(pullErrors)
- }
-
- ir.newImageEvent(events.Pull, images[0])
- return images, nil
-}
-
-// pullGoalFromPossiblyUnqualifiedName looks at inputName and determines the possible
-// image references to try pulling in combination with the registries.conf file as well
-func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(sys *types.SystemContext, writer io.Writer, inputName string) (*pullGoal, error) {
- if sys == nil {
- sys = &types.SystemContext{}
- }
-
- resolved, err := shortnames.Resolve(sys, inputName)
- if err != nil {
- return nil, err
- }
-
- if desc := resolved.Description(); len(desc) > 0 {
- logrus.Debug(desc)
- if writer != nil {
- if _, err := writer.Write([]byte(desc + "\n")); err != nil {
- return nil, err
- }
- }
- }
-
- refPairs := []pullRefPair{}
- for i, candidate := range resolved.PullCandidates {
- srcRef, err := docker.NewReference(candidate.Value)
- if err != nil {
- return nil, err
- }
- ps, err := ir.getPullRefPair(srcRef, candidate.Value.String())
- if err != nil {
- return nil, err
- }
- ps.resolvedShortname = &resolved.PullCandidates[i]
- refPairs = append(refPairs, ps)
- }
- return &pullGoal{
- refPairs: refPairs,
- pullAllPairs: false,
- shortName: inputName,
- resolved: resolved,
- }, nil
-}
-
-// checkRemoteImageForLabel checks if the remote image has a specific label. if the label exists, we
-// return nil, else we return an error
-func checkRemoteImageForLabel(ctx context.Context, label string, imageInfo pullRefPair, sc *types.SystemContext) error {
- labelImage, err := imageInfo.srcRef.NewImage(ctx, sc)
- if err != nil {
- return err
- }
- remoteInspect, err := labelImage.Inspect(ctx)
- if err != nil {
- return err
- }
- // Labels are case insensitive; so we iterate instead of simple lookup
- for k := range remoteInspect.Labels {
- if strings.EqualFold(label, k) {
- return nil
- }
- }
- return errors.Errorf("%s has no label %s in %q", imageInfo.image, label, remoteInspect.Labels)
-}
diff --git a/libpod/image/pull_test.go b/libpod/image/pull_test.go
deleted file mode 100644
index d2930451c..000000000
--- a/libpod/image/pull_test.go
+++ /dev/null
@@ -1,394 +0,0 @@
-package image
-
-import (
- "context"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "strings"
- "testing"
-
- "github.com/containers/image/v5/transports"
- "github.com/containers/image/v5/transports/alltransports"
- "github.com/containers/image/v5/types"
- "github.com/containers/storage"
- "github.com/containers/storage/pkg/idtools"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-// newTestRuntime returns a *Runtime implementation and a cleanup function which the caller is expected to call.
-func newTestRuntime(t *testing.T) (*Runtime, func()) {
- wd, err := ioutil.TempDir("", "testStorageRuntime")
- require.NoError(t, err)
- err = os.MkdirAll(wd, 0700)
- require.NoError(t, err)
-
- store, err := storage.GetStore(storage.StoreOptions{
- RunRoot: filepath.Join(wd, "run"),
- GraphRoot: filepath.Join(wd, "root"),
- GraphDriverName: "vfs",
- GraphDriverOptions: []string{},
- UIDMap: []idtools.IDMap{{
- ContainerID: 0,
- HostID: os.Getuid(),
- Size: 1,
- }},
- GIDMap: []idtools.IDMap{{
- ContainerID: 0,
- HostID: os.Getgid(),
- Size: 1,
- }},
- })
- require.NoError(t, err)
-
- ir := NewImageRuntimeFromStore(store)
- cleanup := func() { _ = os.RemoveAll(wd) }
- return ir, cleanup
-}
-
-// storageReferenceWithoutLocation returns ref.StringWithinTransport(),
-// stripping the [store-specification] prefix from containers/image/storage reference format.
-func storageReferenceWithoutLocation(ref types.ImageReference) string {
- res := ref.StringWithinTransport()
- if res[0] == '[' {
- closeIndex := strings.IndexRune(res, ']')
- if closeIndex > 0 {
- res = res[closeIndex+1:]
- }
- }
- return res
-}
-
-func TestGetPullRefPair(t *testing.T) {
- const imageID = "@0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
- const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
-
- ir, cleanup := newTestRuntime(t)
- defer cleanup()
-
- for _, c := range []struct{ srcName, destName, expectedImage, expectedDstName string }{
- // == Source does not have a Docker reference (as is the case for docker-archive:, oci-archive, dir:); destination formats:
- { // registry/name, no tag:
- "dir:/dev/this-does-not-exist", "example.com/from-directory",
- "example.com/from-directory", "example.com/from-directory:latest",
- },
- { // name, no registry, no tag:
- "dir:/dev/this-does-not-exist", "from-directory",
- "localhost/from-directory", "localhost/from-directory:latest",
- },
- { // registry/name:tag :
- "dir:/dev/this-does-not-exist", "example.com/from-directory:notlatest",
- "example.com/from-directory:notlatest", "example.com/from-directory:notlatest",
- },
- { // name:tag, no registry:
- "dir:/dev/this-does-not-exist", "from-directory:notlatest",
- "localhost/from-directory:notlatest", "localhost/from-directory:notlatest",
- },
- { // name@digest, no registry:
- "dir:/dev/this-does-not-exist", "from-directory" + digestSuffix,
- "localhost/from-directory" + digestSuffix, "localhost/from-directory" + digestSuffix,
- },
- { // registry/name@digest:
- "dir:/dev/this-does-not-exist", "example.com/from-directory" + digestSuffix,
- "example.com/from-directory" + digestSuffix, "example.com/from-directory" + digestSuffix,
- },
- { // ns/name:tag, no registry:
- "dir:/dev/this-does-not-exist", "ns/from-directory:notlatest",
- "localhost/ns/from-directory:notlatest", "localhost/ns/from-directory:notlatest",
- },
- { // containers-storage image ID
- "dir:/dev/this-does-not-exist", imageID,
- imageID, imageID,
- },
- // == Source does have a Docker reference.
- // In that case getPullListFromRef uses the full transport:name input as a destName,
- // which would be invalid in the returned dstName - but dstName is derived from the source, so it does not really matter _so_ much.
- // Note that unlike real-world use we use different :source and :destination to verify the data flow in more detail.
- { // registry/name:tag
- "docker://example.com/busybox:source", "docker://example.com/busybox:destination",
- "docker://example.com/busybox:destination", "example.com/busybox:source",
- },
- { // Implied docker.io/library and :latest
- "docker://busybox", "docker://busybox:destination",
- "docker://busybox:destination", "docker.io/library/busybox:latest",
- },
- // == Invalid destination format.
- {"tarball:/dev/null", "tarball:/dev/null", "", ""},
- } {
- testDescription := fmt.Sprintf("%#v %#v", c.srcName, c.destName)
- srcRef, err := alltransports.ParseImageName(c.srcName)
- require.NoError(t, err, testDescription)
-
- res, err := ir.getPullRefPair(srcRef, c.destName)
- if c.expectedDstName == "" {
- assert.Error(t, err, testDescription)
- } else {
- require.NoError(t, err, testDescription)
- assert.Equal(t, c.expectedImage, res.image, testDescription)
- assert.Equal(t, srcRef, res.srcRef, testDescription)
- assert.Equal(t, c.expectedDstName, storageReferenceWithoutLocation(res.dstRef), testDescription)
- }
- }
-}
-
-func TestPullGoalFromImageReference(t *testing.T) {
- ir, cleanup := newTestRuntime(t)
- defer cleanup()
-
- type expected struct{ image, dstName string }
- for _, c := range []struct {
- srcName string
- expected []expected
- expectedPullAllPairs bool
- }{
- // == docker-archive:
- {"docker-archive:/dev/this-does-not-exist", nil, false}, // Input does not exist.
- {"docker-archive:/dev/null", nil, false}, // Input exists but does not contain a manifest.
- // FIXME: The implementation has extra code for len(manifest) == 0?! That will fail in getImageDigest anyway.
- { // RepoTags is empty
- "docker-archive:testdata/docker-unnamed.tar.xz",
- []expected{{"@ec9293436c2e66da44edb9efb8d41f6b13baf62283ebe846468bc992d76d7951", "@ec9293436c2e66da44edb9efb8d41f6b13baf62283ebe846468bc992d76d7951"}},
- true,
- },
- { // RepoTags is a [docker.io/library/]name:latest, normalized to the short format.
- "docker-archive:testdata/docker-name-only.tar.xz",
- []expected{{"localhost/pretty-empty:latest", "localhost/pretty-empty:latest"}},
- true,
- },
- { // RepoTags is a registry/name:latest
- "docker-archive:testdata/docker-registry-name.tar.xz",
- []expected{{"example.com/empty:latest", "example.com/empty:latest"}},
- true,
- },
- { // RepoTags has multiple items for a single image
- "docker-archive:testdata/docker-two-names.tar.xz",
- []expected{
- {"localhost/pretty-empty:latest", "localhost/pretty-empty:latest"},
- {"example.com/empty:latest", "example.com/empty:latest"},
- },
- true,
- },
- { // Reference image by name in multi-image archive
- "docker-archive:testdata/docker-two-images.tar.xz:example.com/empty:latest",
- []expected{
- {"example.com/empty:latest", "example.com/empty:latest"},
- },
- true,
- },
- { // Reference image by name in multi-image archive
- "docker-archive:testdata/docker-two-images.tar.xz:example.com/empty/but:different",
- []expected{
- {"example.com/empty/but:different", "example.com/empty/but:different"},
- },
- true,
- },
- { // Reference image by index in multi-image archive
- "docker-archive:testdata/docker-two-images.tar.xz:@0",
- []expected{
- {"example.com/empty:latest", "example.com/empty:latest"},
- },
- true,
- },
- { // Reference image by index in multi-image archive
- "docker-archive:testdata/docker-two-images.tar.xz:@1",
- []expected{
- {"example.com/empty/but:different", "example.com/empty/but:different"},
- },
- true,
- },
- { // Reference entire multi-image archive must fail (more than one manifest)
- "docker-archive:testdata/docker-two-images.tar.xz",
- []expected{},
- true,
- },
-
- // == oci-archive:
- {"oci-archive:/dev/this-does-not-exist", nil, false}, // Input does not exist.
- {"oci-archive:/dev/null", nil, false}, // Input exists but does not contain a manifest.
- // FIXME: The remaining tests are commented out for now, because oci-archive: does not work unprivileged.
- // { // No name annotation
- // "oci-archive:testdata/oci-unnamed.tar.gz",
- // []expected{{"@5c8aca8137ac47e84c69ae93ce650ce967917cc001ba7aad5494073fac75b8b6", "@5c8aca8137ac47e84c69ae93ce650ce967917cc001ba7aad5494073fac75b8b6"}},
- // false,
- // },
- // { // Name is a name:latest (no normalization is defined).
- // "oci-archive:testdata/oci-name-only.tar.gz",
- // []expected{{"localhost/pretty-empty:latest", "localhost/pretty-empty:latest"}},
- // false,
- // },
- // { // Name is a registry/name:latest
- // "oci-archive:testdata/oci-registry-name.tar.gz",
- // []expected{{"example.com/empty:latest", "example.com/empty:latest"}},
- // false,
- // },
- // // Name exists, but is an invalid Docker reference; such names will fail when creating dstReference.
- // {"oci-archive:testdata/oci-non-docker-name.tar.gz", nil, false},
- // Maybe test support of two images in a single archive? It should be transparently handled by adding a reference to srcRef.
-
- // == dir:
- { // Absolute path
- "dir:/dev/this-does-not-exist",
- []expected{{"localhost/dev/this-does-not-exist", "localhost/dev/this-does-not-exist:latest"}},
- false,
- },
- { // Relative path, single element.
- "dir:this-does-not-exist",
- []expected{{"localhost/this-does-not-exist", "localhost/this-does-not-exist:latest"}},
- false,
- },
- { // Relative path, multiple elements.
- "dir:testdata/this-does-not-exist",
- []expected{{"localhost/testdata/this-does-not-exist", "localhost/testdata/this-does-not-exist:latest"}},
- false,
- },
-
- // == Others, notably:
- // === docker:// (has ImageReference.DockerReference)
- { // Fully-specified input
- "docker://docker.io/library/busybox:latest",
- []expected{{"docker://docker.io/library/busybox:latest", "docker.io/library/busybox:latest"}},
- false,
- },
- { // Minimal form of the input
- "docker://busybox",
- []expected{{"docker://busybox", "docker.io/library/busybox:latest"}},
- false,
- },
-
- // === tarball: (as an example of what happens when ImageReference.DockerReference is nil).
- // FIXME? This tries to parse "tarball:/dev/null" as a storageReference, and fails.
- // (This is NOT an API promise that the results will continue to be this way.)
- {"tarball:/dev/null", nil, false},
- } {
- srcRef, err := alltransports.ParseImageName(c.srcName)
- require.NoError(t, err, c.srcName)
-
- res, err := ir.pullGoalFromImageReference(context.Background(), srcRef, c.srcName, nil)
- if len(c.expected) == 0 {
- assert.Error(t, err, c.srcName)
- } else {
- require.NoError(t, err, c.srcName)
- require.Len(t, res.refPairs, len(c.expected), c.srcName)
- for i, e := range c.expected {
- testDescription := fmt.Sprintf("%s #%d", c.srcName, i)
- assert.Equal(t, e.image, res.refPairs[i].image, testDescription)
- assert.Equal(t, transports.ImageName(srcRef), transports.ImageName(res.refPairs[i].srcRef), testDescription)
- assert.Equal(t, e.dstName, storageReferenceWithoutLocation(res.refPairs[i].dstRef), testDescription)
- }
- assert.Equal(t, c.expectedPullAllPairs, res.pullAllPairs, c.srcName)
- }
- }
-}
-
-const registriesConfWithSearch = `unqualified-search-registries = ['example.com', 'docker.io']`
-
-func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) {
- const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
- type pullRefStrings struct{ image, srcRef, dstName string } // pullRefPair with string data only
-
- registriesConf, err := ioutil.TempFile("", "TestPullGoalFromPossiblyUnqualifiedName")
- require.NoError(t, err)
- defer registriesConf.Close()
- defer os.Remove(registriesConf.Name())
-
- err = ioutil.WriteFile(registriesConf.Name(), []byte(registriesConfWithSearch), 0600)
- require.NoError(t, err)
-
- ir, cleanup := newTestRuntime(t)
- defer cleanup()
-
- sc := GetSystemContext("", "", false)
-
- aliasesConf, err := ioutil.TempFile("", "short-name-aliases.conf")
- require.NoError(t, err)
- defer aliasesConf.Close()
- defer os.Remove(aliasesConf.Name())
- sc.UserShortNameAliasConfPath = aliasesConf.Name()
- sc.SystemRegistriesConfPath = registriesConf.Name()
-
- // Make sure to not sure the system's registries.conf.d
- dir, err := ioutil.TempDir("", "example")
- require.NoError(t, err)
- sc.SystemRegistriesConfDirPath = dir
- defer os.RemoveAll(dir) // clean up
-
- for _, c := range []struct {
- input string
- expected []pullRefStrings
- }{
- {"#", nil}, // Clearly invalid.
- { // Fully-explicit docker.io, name-only.
- "docker.io/library/busybox",
- // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".)
- []pullRefStrings{{"docker.io/library/busybox:latest", "docker://busybox:latest", "docker.io/library/busybox:latest"}},
- },
- { // docker.io with implied /library/, name-only.
- "docker.io/busybox",
- // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".)
- []pullRefStrings{{"docker.io/library/busybox:latest", "docker://busybox:latest", "docker.io/library/busybox:latest"}},
- },
- { // Qualified example.com, name-only.
- "example.com/ns/busybox",
- []pullRefStrings{{"example.com/ns/busybox:latest", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"}},
- },
- { // Qualified example.com, name:tag.
- "example.com/ns/busybox:notlatest",
- []pullRefStrings{{"example.com/ns/busybox:notlatest", "docker://example.com/ns/busybox:notlatest", "example.com/ns/busybox:notlatest"}},
- },
- { // Qualified example.com, name@digest.
- "example.com/ns/busybox" + digestSuffix,
- []pullRefStrings{{"example.com/ns/busybox" + digestSuffix, "docker://example.com/ns/busybox" + digestSuffix,
- "example.com/ns/busybox" + digestSuffix}},
- },
- // Qualified example.com, name:tag@digest. This code is happy to try, but .srcRef parsing currently rejects such input.
- {"example.com/ns/busybox:notlatest" + digestSuffix, nil},
- { // Unqualified, single-name, name-only
- "busybox",
- []pullRefStrings{
- {"example.com/busybox:latest", "docker://example.com/busybox:latest", "example.com/busybox:latest"},
- // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".)
- {"docker.io/library/busybox:latest", "docker://busybox:latest", "docker.io/library/busybox:latest"},
- },
- },
- { // Unqualified, namespaced, name-only
- "ns/busybox",
- []pullRefStrings{
- {"example.com/ns/busybox:latest", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"},
- },
- },
- { // Unqualified, name:tag
- "busybox:notlatest",
- []pullRefStrings{
- {"example.com/busybox:notlatest", "docker://example.com/busybox:notlatest", "example.com/busybox:notlatest"},
- // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".)
- {"docker.io/library/busybox:notlatest", "docker://busybox:notlatest", "docker.io/library/busybox:notlatest"},
- },
- },
- { // Unqualified, name@digest
- "busybox" + digestSuffix,
- []pullRefStrings{
- {"example.com/busybox" + digestSuffix, "docker://example.com/busybox" + digestSuffix, "example.com/busybox" + digestSuffix},
- // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".)
- {"docker.io/library/busybox" + digestSuffix, "docker://busybox" + digestSuffix, "docker.io/library/busybox" + digestSuffix},
- },
- },
- // Unqualified, name:tag@digest. This code is happy to try, but .srcRef parsing currently rejects such input.
- {"busybox:notlatest" + digestSuffix, nil},
- } {
- res, err := ir.pullGoalFromPossiblyUnqualifiedName(sc, nil, c.input)
- if len(c.expected) == 0 {
- assert.Error(t, err, c.input)
- } else {
- assert.NoError(t, err, c.input)
- for i, e := range c.expected {
- testDescription := fmt.Sprintf("%s #%d (%v)", c.input, i, res.refPairs)
- assert.Equal(t, e.image, res.refPairs[i].image, testDescription)
- assert.Equal(t, e.srcRef, transports.ImageName(res.refPairs[i].srcRef), testDescription)
- assert.Equal(t, e.dstName, storageReferenceWithoutLocation(res.refPairs[i].dstRef), testDescription)
- }
- assert.False(t, res.pullAllPairs, c.input)
- }
- }
-}
diff --git a/libpod/image/search.go b/libpod/image/search.go
deleted file mode 100644
index 714551e6e..000000000
--- a/libpod/image/search.go
+++ /dev/null
@@ -1,318 +0,0 @@
-package image
-
-import (
- "context"
- "fmt"
- "strconv"
- "strings"
- "sync"
-
- "github.com/containers/image/v5/docker"
- "github.com/containers/image/v5/transports/alltransports"
- "github.com/containers/image/v5/types"
- sysreg "github.com/containers/podman/v3/pkg/registries"
- "github.com/pkg/errors"
- "github.com/sirupsen/logrus"
- "golang.org/x/sync/semaphore"
-)
-
-const (
- descriptionTruncLength = 44
- maxQueries = 25
- maxParallelSearches = int64(6)
-)
-
-// SearchResult is holding image-search related data.
-type SearchResult struct {
- // Index is the image index (e.g., "docker.io" or "quay.io")
- Index string
- // Name is the canonical name of the image (e.g., "docker.io/library/alpine").
- Name string
- // Description of the image.
- Description string
- // Stars is the number of stars of the image.
- Stars int
- // Official indicates if it's an official image.
- Official string
- // Automated indicates if the image was created by an automated build.
- Automated string
- // Tag is the image tag
- Tag string
-}
-
-// SearchOptions are used to control the behaviour of SearchImages.
-type SearchOptions struct {
- // Filter allows to filter the results.
- Filter SearchFilter
- // Limit limits the number of queries per index (default: 25). Must be
- // greater than 0 to overwrite the default value.
- Limit int
- // NoTrunc avoids the output to be truncated.
- NoTrunc bool
- // Authfile is the path to the authentication file.
- Authfile string
- // InsecureSkipTLSVerify allows to skip TLS verification.
- InsecureSkipTLSVerify types.OptionalBool
- // ListTags returns the search result with available tags
- ListTags bool
-}
-
-// SearchFilter allows filtering the results of SearchImages.
-type SearchFilter struct {
- // Stars describes the minimal amount of starts of an image.
- Stars int
- // IsAutomated decides if only images from automated builds are displayed.
- IsAutomated types.OptionalBool
- // IsOfficial decides if only official images are displayed.
- IsOfficial types.OptionalBool
-}
-
-// SearchImages searches images based on term and the specified SearchOptions
-// in all registries.
-func SearchImages(term string, options SearchOptions) ([]SearchResult, error) {
- registry := ""
-
- // Try to extract a registry from the specified search term. We
- // consider everything before the first slash to be the registry. Note
- // that we cannot use the reference parser from the containers/image
- // library as the search term may container arbitrary input such as
- // wildcards. See bugzilla.redhat.com/show_bug.cgi?id=1846629.
- if spl := strings.SplitN(term, "/", 2); len(spl) > 1 {
- registry = spl[0]
- term = spl[1]
- }
-
- registries, err := getRegistries(registry)
- if err != nil {
- return nil, err
- }
-
- // searchOutputData is used as a return value for searching in parallel.
- type searchOutputData struct {
- data []SearchResult
- err error
- }
-
- // Let's follow Firefox by limiting parallel downloads to 6.
- sem := semaphore.NewWeighted(maxParallelSearches)
- wg := sync.WaitGroup{}
- wg.Add(len(registries))
- data := make([]searchOutputData, len(registries))
-
- searchImageInRegistryHelper := func(index int, registry string) {
- defer sem.Release(1)
- defer wg.Done()
- searchOutput, err := searchImageInRegistry(term, registry, options)
- data[index] = searchOutputData{data: searchOutput, err: err}
- }
-
- ctx := context.Background()
- for i := range registries {
- if err := sem.Acquire(ctx, 1); err != nil {
- return nil, err
- }
- go searchImageInRegistryHelper(i, registries[i])
- }
-
- wg.Wait()
- results := []SearchResult{}
- var lastError error
- for _, d := range data {
- if d.err != nil {
- if lastError != nil {
- logrus.Errorf("%v", lastError)
- }
- lastError = d.err
- continue
- }
- results = append(results, d.data...)
- }
- if len(results) > 0 {
- return results, nil
- }
- return results, lastError
-}
-
-// getRegistries returns the list of registries to search, depending on an optional registry specification
-func getRegistries(registry string) ([]string, error) {
- var registries []string
- if registry != "" {
- registries = append(registries, registry)
- } else {
- var err error
- registries, err = sysreg.GetRegistries()
- if err != nil {
- return nil, errors.Wrapf(err, "error getting registries to search")
- }
- }
- return registries, nil
-}
-
-func searchImageInRegistry(term string, registry string, options SearchOptions) ([]SearchResult, error) {
- // Max number of queries by default is 25
- limit := maxQueries
- if options.Limit > 0 {
- limit = options.Limit
- }
-
- sc := GetSystemContext("", options.Authfile, false)
- sc.DockerInsecureSkipTLSVerify = options.InsecureSkipTLSVerify
- // FIXME: Set this more globally. Probably no reason not to have it in
- // every types.SystemContext, and to compute the value just once in one
- // place.
- sc.SystemRegistriesConfPath = sysreg.SystemRegistriesConfPath()
- if options.ListTags {
- results, err := searchRepositoryTags(registry, term, sc, options)
- if err != nil {
- return []SearchResult{}, err
- }
- return results, nil
- }
-
- results, err := docker.SearchRegistry(context.TODO(), sc, registry, term, limit)
- if err != nil {
- return []SearchResult{}, err
- }
- index := registry
- arr := strings.Split(registry, ".")
- if len(arr) > 2 {
- index = strings.Join(arr[len(arr)-2:], ".")
- }
-
- // limit is the number of results to output
- // if the total number of results is less than the limit, output all
- // if the limit has been set by the user, output those number of queries
- limit = maxQueries
- if len(results) < limit {
- limit = len(results)
- }
- if options.Limit != 0 {
- limit = len(results)
- if options.Limit < len(results) {
- limit = options.Limit
- }
- }
-
- paramsArr := []SearchResult{}
- for i := 0; i < limit; i++ {
- // Check whether query matches filters
- if !(options.Filter.matchesAutomatedFilter(results[i]) && options.Filter.matchesOfficialFilter(results[i]) && options.Filter.matchesStarFilter(results[i])) {
- continue
- }
- official := ""
- if results[i].IsOfficial {
- official = "[OK]"
- }
- automated := ""
- if results[i].IsAutomated {
- automated = "[OK]"
- }
- description := strings.Replace(results[i].Description, "\n", " ", -1)
- if len(description) > 44 && !options.NoTrunc {
- description = description[:descriptionTruncLength] + "..."
- }
- name := registry + "/" + results[i].Name
- if index == "docker.io" && !strings.Contains(results[i].Name, "/") {
- name = index + "/library/" + results[i].Name
- }
- params := SearchResult{
- Index: index,
- Name: name,
- Description: description,
- Official: official,
- Automated: automated,
- Stars: results[i].StarCount,
- }
- paramsArr = append(paramsArr, params)
- }
- return paramsArr, nil
-}
-
-func searchRepositoryTags(registry, term string, sc *types.SystemContext, options SearchOptions) ([]SearchResult, error) {
- dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name())
- imageRef, err := alltransports.ParseImageName(fmt.Sprintf("%s/%s", registry, term))
- if err == nil && imageRef.Transport().Name() != docker.Transport.Name() {
- return nil, errors.Errorf("reference %q must be a docker reference", term)
- } else if err != nil {
- imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, fmt.Sprintf("%s/%s", registry, term)))
- if err != nil {
- return nil, errors.Errorf("reference %q must be a docker reference", term)
- }
- }
- tags, err := docker.GetRepositoryTags(context.TODO(), sc, imageRef)
- if err != nil {
- return nil, errors.Errorf("error getting repository tags: %v", err)
- }
- limit := maxQueries
- if len(tags) < limit {
- limit = len(tags)
- }
- if options.Limit != 0 {
- limit = len(tags)
- if options.Limit < limit {
- limit = options.Limit
- }
- }
- paramsArr := []SearchResult{}
- for i := 0; i < limit; i++ {
- params := SearchResult{
- Name: imageRef.DockerReference().Name(),
- Tag: tags[i],
- }
- paramsArr = append(paramsArr, params)
- }
- return paramsArr, nil
-}
-
-// ParseSearchFilter turns the filter into a SearchFilter that can be used for
-// searching images.
-func ParseSearchFilter(filter []string) (*SearchFilter, error) {
- sFilter := new(SearchFilter)
- for _, f := range filter {
- arr := strings.SplitN(f, "=", 2)
- switch arr[0] {
- case "stars":
- if len(arr) < 2 {
- return nil, errors.Errorf("invalid `stars` filter %q, should be stars=<value>", filter)
- }
- stars, err := strconv.Atoi(arr[1])
- if err != nil {
- return nil, errors.Wrapf(err, "incorrect value type for stars filter")
- }
- sFilter.Stars = stars
- case "is-automated":
- if len(arr) == 2 && arr[1] == "false" {
- sFilter.IsAutomated = types.OptionalBoolFalse
- } else {
- sFilter.IsAutomated = types.OptionalBoolTrue
- }
- case "is-official":
- if len(arr) == 2 && arr[1] == "false" {
- sFilter.IsOfficial = types.OptionalBoolFalse
- } else {
- sFilter.IsOfficial = types.OptionalBoolTrue
- }
- default:
- return nil, errors.Errorf("invalid filter type %q", f)
- }
- }
- return sFilter, nil
-}
-
-func (f *SearchFilter) matchesStarFilter(result docker.SearchResult) bool {
- return result.StarCount >= f.Stars
-}
-
-func (f *SearchFilter) matchesAutomatedFilter(result docker.SearchResult) bool {
- if f.IsAutomated != types.OptionalBoolUndefined {
- return result.IsAutomated == (f.IsAutomated == types.OptionalBoolTrue)
- }
- return true
-}
-
-func (f *SearchFilter) matchesOfficialFilter(result docker.SearchResult) bool {
- if f.IsOfficial != types.OptionalBoolUndefined {
- return result.IsOfficial == (f.IsOfficial == types.OptionalBoolTrue)
- }
- return true
-}
diff --git a/libpod/image/signing_options.go b/libpod/image/signing_options.go
deleted file mode 100644
index f310da749..000000000
--- a/libpod/image/signing_options.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package image
-
-// SigningOptions encapsulates settings that control whether or not we strip or
-// add signatures to images when writing them.
-type SigningOptions struct {
- // RemoveSignatures directs us to remove any signatures which are already present.
- RemoveSignatures bool
- // SignBy is a key identifier of some kind, indicating that a signature should be generated using the specified private key and stored with the image.
- SignBy string
-}
diff --git a/libpod/image/testdata/docker-name-only.tar.xz b/libpod/image/testdata/docker-name-only.tar.xz
deleted file mode 100644
index 0cad9f108..000000000
--- a/libpod/image/testdata/docker-name-only.tar.xz
+++ /dev/null
Binary files differ
diff --git a/libpod/image/testdata/docker-registry-name.tar.xz b/libpod/image/testdata/docker-registry-name.tar.xz
deleted file mode 100644
index 181816c2e..000000000
--- a/libpod/image/testdata/docker-registry-name.tar.xz
+++ /dev/null
Binary files differ
diff --git a/libpod/image/testdata/docker-two-images.tar.xz b/libpod/image/testdata/docker-two-images.tar.xz
deleted file mode 100644
index 148d8a86b..000000000
--- a/libpod/image/testdata/docker-two-images.tar.xz
+++ /dev/null
Binary files differ
diff --git a/libpod/image/testdata/docker-two-names.tar.xz b/libpod/image/testdata/docker-two-names.tar.xz
deleted file mode 100644
index 07fbc479c..000000000
--- a/libpod/image/testdata/docker-two-names.tar.xz
+++ /dev/null
Binary files differ
diff --git a/libpod/image/testdata/docker-unnamed.tar.xz b/libpod/image/testdata/docker-unnamed.tar.xz
deleted file mode 100644
index ba6ea1bae..000000000
--- a/libpod/image/testdata/docker-unnamed.tar.xz
+++ /dev/null
Binary files differ
diff --git a/libpod/image/testdata/oci-name-only.tar.gz b/libpod/image/testdata/oci-name-only.tar.gz
deleted file mode 100644
index 57bc07564..000000000
--- a/libpod/image/testdata/oci-name-only.tar.gz
+++ /dev/null
Binary files differ
diff --git a/libpod/image/testdata/oci-non-docker-name.tar.gz b/libpod/image/testdata/oci-non-docker-name.tar.gz
deleted file mode 100644
index 5ffc0eabd..000000000
--- a/libpod/image/testdata/oci-non-docker-name.tar.gz
+++ /dev/null
Binary files differ
diff --git a/libpod/image/testdata/oci-registry-name.tar.gz b/libpod/image/testdata/oci-registry-name.tar.gz
deleted file mode 100644
index e6df87339..000000000
--- a/libpod/image/testdata/oci-registry-name.tar.gz
+++ /dev/null
Binary files differ
diff --git a/libpod/image/testdata/oci-unnamed.tar.gz b/libpod/image/testdata/oci-unnamed.tar.gz
deleted file mode 100644
index de445fdf8..000000000
--- a/libpod/image/testdata/oci-unnamed.tar.gz
+++ /dev/null
Binary files differ
diff --git a/libpod/image/testdata/registries.conf b/libpod/image/testdata/registries.conf
deleted file mode 100644
index 16622a1ac..000000000
--- a/libpod/image/testdata/registries.conf
+++ /dev/null
@@ -1,4 +0,0 @@
-short-name-mode="enforcing"
-
-[aliases]
-"busybox"="docker.io/library/busybox"
diff --git a/libpod/image/tree.go b/libpod/image/tree.go
deleted file mode 100644
index c7c69462f..000000000
--- a/libpod/image/tree.go
+++ /dev/null
@@ -1,138 +0,0 @@
-package image
-
-import (
- "context"
- "fmt"
- "strings"
-
- "github.com/docker/go-units"
- "github.com/pkg/errors"
-)
-
-const (
- middleItem = "├── "
- continueItem = "│ "
- lastItem = "└── "
-)
-
-type tree struct {
- img *Image
- imageInfo *InfoImage
- layerInfo map[string]*LayerInfo
- sb *strings.Builder
-}
-
-// GenerateTree creates an image tree string representation for displaying it
-// to the user.
-func (i *Image) GenerateTree(whatRequires bool) (string, error) {
- // Fetch map of image-layers, which is used for printing output.
- layerInfo, err := GetLayersMapWithImageInfo(i.imageruntime)
- if err != nil {
- return "", errors.Wrapf(err, "error while retrieving layers of image %q", i.InputName)
- }
-
- // Create an imageInfo and fill the image and layer info
- imageInfo := &InfoImage{
- ID: i.ID(),
- Tags: i.Names(),
- }
-
- if err := BuildImageHierarchyMap(imageInfo, layerInfo, i.TopLayer()); err != nil {
- return "", err
- }
- sb := &strings.Builder{}
- tree := &tree{i, imageInfo, layerInfo, sb}
- if err := tree.print(whatRequires); err != nil {
- return "", err
- }
- return tree.string(), nil
-}
-
-func (t *tree) string() string {
- return t.sb.String()
-}
-
-func (t *tree) print(whatRequires bool) error {
- size, err := t.img.Size(context.Background())
- if err != nil {
- return err
- }
-
- fmt.Fprintf(t.sb, "Image ID: %s\n", t.imageInfo.ID[:12])
- fmt.Fprintf(t.sb, "Tags: %s\n", t.imageInfo.Tags)
- fmt.Fprintf(t.sb, "Size: %v\n", units.HumanSizeWithPrecision(float64(*size), 4))
- if t.img.TopLayer() != "" {
- fmt.Fprintf(t.sb, "Image Layers\n")
- } else {
- fmt.Fprintf(t.sb, "No Image Layers\n")
- }
-
- if !whatRequires {
- // fill imageInfo with layers associated with image.
- // the layers will be filled such that
- // (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End)
- // Build output from imageInfo into buffer
- t.printImageHierarchy(t.imageInfo)
- } else {
- // fill imageInfo with layers associated with image.
- // the layers will be filled such that
- // (Start)TopLayer->...intermediate Child Layer(s)-> Child TopLayer(End)
- // (Forks)... intermediate Child Layer(s) -> Child Top Layer(End)
- return t.printImageChildren(t.layerInfo, t.img.TopLayer(), "", true)
- }
- return nil
-}
-
-// Stores all children layers which are created using given Image.
-// Layers are stored as follows
-// (Start)TopLayer->...intermediate Child Layer(s)-> Child TopLayer(End)
-// (Forks)... intermediate Child Layer(s) -> Child Top Layer(End)
-func (t *tree) printImageChildren(layerMap map[string]*LayerInfo, layerID string, prefix string, last bool) error {
- if layerID == "" {
- return nil
- }
- ll, ok := layerMap[layerID]
- if !ok {
- return fmt.Errorf("lookup error: layerid %s, not found", layerID)
- }
- fmt.Fprint(t.sb, prefix)
-
- //initialize intend with middleItem to reduce middleItem checks.
- intend := middleItem
- if !last {
- // add continueItem i.e. '|' for next iteration prefix
- prefix += continueItem
- } else if len(ll.ChildID) > 1 || len(ll.ChildID) == 0 {
- // The above condition ensure, alignment happens for node, which has more then 1 children.
- // If node is last in printing hierarchy, it should not be printed as middleItem i.e. ├──
- intend = lastItem
- prefix += " "
- }
-
- var tags string
- if len(ll.RepoTags) > 0 {
- tags = fmt.Sprintf(" Top Layer of: %s", ll.RepoTags)
- }
- fmt.Fprintf(t.sb, "%sID: %s Size: %7v%s\n", intend, ll.ID[:12], units.HumanSizeWithPrecision(float64(ll.Size), 4), tags)
- for count, childID := range ll.ChildID {
- if err := t.printImageChildren(layerMap, childID, prefix, count == len(ll.ChildID)-1); err != nil {
- return err
- }
- }
- return nil
-}
-
-// prints the layers info of image
-func (t *tree) printImageHierarchy(imageInfo *InfoImage) {
- for count, l := range imageInfo.Layers {
- var tags string
- intend := middleItem
- if len(l.RepoTags) > 0 {
- tags = fmt.Sprintf(" Top Layer of: %s", l.RepoTags)
- }
- if count == len(imageInfo.Layers)-1 {
- intend = lastItem
- }
- fmt.Fprintf(t.sb, "%s ID: %s Size: %7v%s\n", intend, l.ID[:12], units.HumanSizeWithPrecision(float64(l.Size), 4), tags)
- }
-}
diff --git a/libpod/image/utils.go b/libpod/image/utils.go
deleted file mode 100644
index dfe35c017..000000000
--- a/libpod/image/utils.go
+++ /dev/null
@@ -1,182 +0,0 @@
-package image
-
-import (
- "fmt"
- "io"
- "net/url"
- "regexp"
- "strings"
-
- cp "github.com/containers/image/v5/copy"
- "github.com/containers/image/v5/docker/reference"
- "github.com/containers/image/v5/signature"
- "github.com/containers/image/v5/types"
- "github.com/containers/podman/v3/libpod/define"
- "github.com/containers/storage"
- "github.com/pkg/errors"
-)
-
-// findImageInRepotags takes an imageParts struct and searches images' repotags for
-// a match on name:tag
-func findImageInRepotags(search imageParts, images []*Image) (*storage.Image, error) {
- _, searchName, searchSuspiciousTagValueForSearch := search.suspiciousRefNameTagValuesForSearch()
- type Candidate struct {
- name string
- image *Image
- }
- var candidates []Candidate
- for _, image := range images {
- for _, name := range image.Names() {
- d, err := decompose(name)
- // if we get an error, ignore and keep going
- if err != nil {
- continue
- }
- _, dName, dSuspiciousTagValueForSearch := d.suspiciousRefNameTagValuesForSearch()
- if dSuspiciousTagValueForSearch != searchSuspiciousTagValueForSearch {
- continue
- }
- if dName == searchName || strings.HasSuffix(dName, "/"+searchName) {
- candidates = append(candidates, Candidate{
- name: name,
- image: image,
- })
- }
- }
- }
- if len(candidates) == 0 {
- return nil, errors.Wrapf(define.ErrNoSuchImage, "unable to find a name and tag match for %s in repotags", searchName)
- }
-
- // If more then one candidate and the candidates all have same name
- // and only one is read/write return it.
- // Otherwise return error with the list of candidates
- if len(candidates) > 1 {
- var (
- rwImage *Image
- rwImageCnt int
- )
- names := make(map[string]bool)
- for _, c := range candidates {
- names[c.name] = true
- if !c.image.IsReadOnly() {
- rwImageCnt++
- rwImage = c.image
- }
- }
- // If only one name used and have read/write image return it
- if len(names) == 1 && rwImageCnt == 1 {
- return rwImage.image, nil
- }
- keys := []string{}
- for k := range names {
- keys = append(keys, k)
- }
- if rwImageCnt > 1 {
- return nil, errors.Wrapf(define.ErrMultipleImages, "found multiple read/write images %s", strings.Join(keys, ","))
- }
- return nil, errors.Wrapf(define.ErrMultipleImages, "found multiple read/only images %s", strings.Join(keys, ","))
- }
- return candidates[0].image.image, nil
-}
-
-// getCopyOptions constructs a new containers/image/copy.Options{} struct from the given parameters, inheriting some from sc.
-func getCopyOptions(sc *types.SystemContext, reportWriter io.Writer, srcDockerRegistry, destDockerRegistry *DockerRegistryOptions, signing SigningOptions, manifestType string, additionalDockerArchiveTags []reference.NamedTagged) *cp.Options {
- if srcDockerRegistry == nil {
- srcDockerRegistry = &DockerRegistryOptions{}
- }
- if destDockerRegistry == nil {
- destDockerRegistry = &DockerRegistryOptions{}
- }
- srcContext := srcDockerRegistry.GetSystemContext(sc, additionalDockerArchiveTags)
- destContext := destDockerRegistry.GetSystemContext(sc, additionalDockerArchiveTags)
- return &cp.Options{
- RemoveSignatures: signing.RemoveSignatures,
- SignBy: signing.SignBy,
- ReportWriter: reportWriter,
- SourceCtx: srcContext,
- DestinationCtx: destContext,
- ForceManifestMIMEType: manifestType,
- }
-}
-
-// getPolicyContext sets up, initializes and returns a new context for the specified policy
-func getPolicyContext(ctx *types.SystemContext) (*signature.PolicyContext, error) {
- policy, err := signature.DefaultPolicy(ctx)
- if err != nil {
- return nil, err
- }
-
- policyContext, err := signature.NewPolicyContext(policy)
- if err != nil {
- return nil, err
- }
- return policyContext, nil
-}
-
-// hasTransport determines if the image string contains '://', returns bool
-func hasTransport(image string) bool {
- return strings.Contains(image, "://")
-}
-
-// GetAdditionalTags returns a list of reference.NamedTagged for the
-// additional tags given in images
-func GetAdditionalTags(images []string) ([]reference.NamedTagged, error) {
- var allTags []reference.NamedTagged
- for _, img := range images {
- ref, err := reference.ParseNormalizedNamed(img)
- if err != nil {
- return nil, errors.Wrapf(err, "error parsing additional tags")
- }
- refTagged, isTagged := ref.(reference.NamedTagged)
- if isTagged {
- allTags = append(allTags, refTagged)
- }
- }
- return allTags, nil
-}
-
-// IsValidImageURI checks if image name has valid format
-func IsValidImageURI(imguri string) (bool, error) {
- uri := "http://" + imguri
- u, err := url.Parse(uri)
- if err != nil {
- return false, errors.Wrapf(err, "invalid image uri: %s", imguri)
- }
- reg := regexp.MustCompile(`^[a-zA-Z0-9-_\.]+\/?:?[0-9]*[a-z0-9-\/:]*$`)
- ret := reg.FindAllString(u.Host, -1)
- if len(ret) == 0 {
- return false, errors.Wrapf(err, "invalid image uri: %s", imguri)
- }
- reg = regexp.MustCompile(`^[a-z0-9-:\./]*$`)
- ret = reg.FindAllString(u.Fragment, -1)
- if len(ret) == 0 {
- return false, errors.Wrapf(err, "invalid image uri: %s", imguri)
- }
- return true, nil
-}
-
-// imageNameForSaveDestination returns a Docker-like reference appropriate for saving img,
-// which the user referred to as imgUserInput; or an empty string, if there is no appropriate
-// reference.
-func imageNameForSaveDestination(img *Image, imgUserInput string) string {
- if strings.Contains(img.ID(), imgUserInput) {
- return ""
- }
-
- prepend := ""
- localRegistryPrefix := fmt.Sprintf("%s/", DefaultLocalRegistry)
- if !strings.HasPrefix(imgUserInput, localRegistryPrefix) {
- // we need to check if localhost was added to the image name in NewFromLocal
- for _, name := range img.Names() {
- // If the user is saving an image in the localhost registry, getLocalImage need
- // a name that matches the format localhost/<tag1>:<tag2> or localhost/<tag>:latest to correctly
- // set up the manifest and save.
- if strings.HasPrefix(name, localRegistryPrefix) && (strings.HasSuffix(name, imgUserInput) || strings.HasSuffix(name, fmt.Sprintf("%s:latest", imgUserInput))) {
- prepend = localRegistryPrefix
- break
- }
- }
- }
- return fmt.Sprintf("%s%s", prepend, imgUserInput)
-}