summaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
Diffstat (limited to 'libpod')
-rw-r--r--libpod/adapter/runtime.go50
-rw-r--r--libpod/adapter/runtime_remote.go181
-rw-r--r--libpod/container.go3
-rw-r--r--libpod/container_api.go20
-rw-r--r--libpod/container_attach_linux.go (renamed from libpod/container_attach.go)21
-rw-r--r--libpod/container_attach_unsupported.go11
-rw-r--r--libpod/container_internal.go2
-rw-r--r--libpod/image/image.go61
-rw-r--r--libpod/image/image_test.go9
-rw-r--r--libpod/image/parts.go125
-rw-r--r--libpod/image/parts_test.go118
-rw-r--r--libpod/image/pull.go59
-rw-r--r--libpod/image/pull_test.go28
-rw-r--r--libpod/image/utils.go12
-rw-r--r--libpod/info.go6
-rw-r--r--libpod/lock/shm_lock_manager_unsupported.go4
-rw-r--r--libpod/oci.go6
-rw-r--r--libpod/options.go6
-rw-r--r--libpod/runtime.go77
-rw-r--r--libpod/runtime_ctr.go2
-rw-r--r--libpod/runtime_volume_unsupported.go19
21 files changed, 548 insertions, 272 deletions
diff --git a/libpod/adapter/runtime.go b/libpod/adapter/runtime.go
index 13141f886..1f3599082 100644
--- a/libpod/adapter/runtime.go
+++ b/libpod/adapter/runtime.go
@@ -3,6 +3,10 @@
package adapter
import (
+ "context"
+ "io"
+
+ "github.com/containers/image/types"
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
@@ -11,8 +15,8 @@ import (
// LocalRuntime describes a typical libpod runtime
type LocalRuntime struct {
- Runtime *libpod.Runtime
- Remote bool
+ *libpod.Runtime
+ Remote bool
}
// ContainerImage ...
@@ -20,6 +24,11 @@ type ContainerImage struct {
*image.Image
}
+// Container ...
+type Container struct {
+ *libpod.Container
+}
+
// GetRuntime returns a LocalRuntime struct with the actual runtime embedded in it
func GetRuntime(c *cli.Context) (*LocalRuntime, error) {
runtime, err := libpodruntime.GetRuntime(c)
@@ -53,3 +62,40 @@ func (r *LocalRuntime) NewImageFromLocal(name string) (*ContainerImage, error) {
}
return &ContainerImage{img}, nil
}
+
+// LoadFromArchiveReference calls into local storage to load an image from an archive
+func (r *LocalRuntime) LoadFromArchiveReference(ctx context.Context, srcRef types.ImageReference, signaturePolicyPath string, writer io.Writer) ([]*ContainerImage, error) {
+ var containerImages []*ContainerImage
+ imgs, err := r.Runtime.ImageRuntime().LoadFromArchiveReference(ctx, srcRef, signaturePolicyPath, writer)
+ if err != nil {
+ return nil, err
+ }
+ for _, i := range imgs {
+ ci := ContainerImage{i}
+ containerImages = append(containerImages, &ci)
+ }
+ return containerImages, nil
+}
+
+// New calls into local storage to look for an image in local storage or to pull it
+func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *image.DockerRegistryOptions, signingoptions image.SigningOptions, forcePull bool) (*ContainerImage, error) {
+ img, err := r.Runtime.ImageRuntime().New(ctx, name, signaturePolicyPath, authfile, writer, dockeroptions, signingoptions, forcePull)
+ if err != nil {
+ return nil, err
+ }
+ return &ContainerImage{img}, nil
+}
+
+// RemoveImage calls into local storage and removes an image
+func (r *LocalRuntime) RemoveImage(ctx context.Context, img *ContainerImage, force bool) (string, error) {
+ return r.Runtime.RemoveImage(ctx, img.Image, force)
+}
+
+// LookupContainer ...
+func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) {
+ ctr, err := r.Runtime.LookupContainer(idOrName)
+ if err != nil {
+ return nil, err
+ }
+ return &Container{ctr}, nil
+}
diff --git a/libpod/adapter/runtime_remote.go b/libpod/adapter/runtime_remote.go
index 2f22dd36b..8ef8fe167 100644
--- a/libpod/adapter/runtime_remote.go
+++ b/libpod/adapter/runtime_remote.go
@@ -5,13 +5,16 @@ package adapter
import (
"context"
"fmt"
- "github.com/containers/libpod/cmd/podman/varlink"
+ "io"
+ "strings"
+ "time"
+
+ "github.com/containers/image/types"
+ iopodman "github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod/image"
- "github.com/opencontainers/go-digest"
+ digest "github.com/opencontainers/go-digest"
"github.com/urfave/cli"
"github.com/varlink/go/varlink"
- "strings"
- "time"
)
// ImageRuntime is wrapper for image runtime
@@ -19,13 +22,13 @@ type RemoteImageRuntime struct{}
// RemoteRuntime describes a wrapper runtime struct
type RemoteRuntime struct {
+ Conn *varlink.Connection
+ Remote bool
}
// LocalRuntime describes a typical libpod runtime
type LocalRuntime struct {
- Runtime *RemoteRuntime
- Remote bool
- Conn *varlink.Connection
+ *RemoteRuntime
}
// GetRuntime returns a LocalRuntime struct with the actual runtime embedded in it
@@ -35,11 +38,14 @@ func GetRuntime(c *cli.Context) (*LocalRuntime, error) {
if err != nil {
return nil, err
}
- return &LocalRuntime{
- Runtime: &runtime,
- Remote: true,
- Conn: conn,
- }, nil
+ rr := RemoteRuntime{
+ Conn: conn,
+ Remote: true,
+ }
+ foo := LocalRuntime{
+ &rr,
+ }
+ return &foo, nil
}
// Shutdown is a bogus wrapper for compat with the libpod runtime
@@ -59,13 +65,36 @@ type remoteImage struct {
RepoDigests []string
Parent string
Size int64
- Tag string
- Repository string
Created time.Time
InputName string
Names []string
Digest digest.Digest
isParent bool
+ Runtime *LocalRuntime
+}
+
+// Container ...
+type Container struct {
+ remoteContainer
+}
+
+// remoteContainer ....
+type remoteContainer struct {
+ ID string
+ Image string
+ ImageID string
+ Command []string
+ Created time.Time
+ RunningFor string
+ Status string
+ //Ports []ocicni.PortMapping
+ RootFsSize int64
+ RWSize int64
+ Names string
+ Labels []map[string]string
+ // Mounts []string
+ // ContainerRunning bool
+ //Namespaces []LinuxNameSpace
}
// GetImages returns a slice of containerimages over a varlink connection
@@ -80,7 +109,7 @@ func (r *LocalRuntime) GetImages() ([]*ContainerImage, error) {
if len(i.RepoTags) > 1 {
name = i.RepoTags[0]
}
- newImage, err := imageInListToContainerImage(i, name)
+ newImage, err := imageInListToContainerImage(i, name, r)
if err != nil {
return nil, err
}
@@ -89,11 +118,7 @@ func (r *LocalRuntime) GetImages() ([]*ContainerImage, error) {
return newImages, nil
}
-func imageInListToContainerImage(i iopodman.ImageInList, name string) (*ContainerImage, error) {
- imageParts, err := image.DecomposeString(name)
- if err != nil {
- return nil, err
- }
+func imageInListToContainerImage(i iopodman.ImageInList, name string, runtime *LocalRuntime) (*ContainerImage, error) {
created, err := splitStringDate(i.Created)
if err != nil {
return nil, err
@@ -107,10 +132,9 @@ func imageInListToContainerImage(i iopodman.ImageInList, name string) (*Containe
Parent: i.ParentId,
Size: i.Size,
Created: created,
- Tag: imageParts.Tag,
- Repository: imageParts.Registry,
Names: i.RepoTags,
isParent: i.IsParent,
+ Runtime: runtime,
}
return &ContainerImage{ri}, nil
}
@@ -121,10 +145,46 @@ func (r *LocalRuntime) NewImageFromLocal(name string) (*ContainerImage, error) {
if err != nil {
return nil, err
}
- return imageInListToContainerImage(img, name)
+ return imageInListToContainerImage(img, name, r)
}
+// LoadFromArchiveReference creates an image from a local archive
+func (r *LocalRuntime) LoadFromArchiveReference(ctx context.Context, srcRef types.ImageReference, signaturePolicyPath string, writer io.Writer) ([]*ContainerImage, error) {
+ // TODO We need to find a way to leak certDir, creds, and the tlsverify into this function, normally this would
+ // come from cli options but we don't want want those in here either.
+ imageID, err := iopodman.PullImage().Call(r.Conn, srcRef.DockerReference().String(), "", "", signaturePolicyPath, true)
+ if err != nil {
+ return nil, err
+ }
+ newImage, err := r.NewImageFromLocal(imageID)
+ if err != nil {
+ return nil, err
+ }
+ return []*ContainerImage{newImage}, nil
+}
+
+// New calls into local storage to look for an image in local storage or to pull it
+func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *image.DockerRegistryOptions, signingoptions image.SigningOptions, forcePull bool) (*ContainerImage, error) {
+ // TODO Creds needs to be figured out here too, like above
+ tlsBool := dockeroptions.DockerInsecureSkipTLSVerify
+ // Remember SkipTlsVerify is the opposite of tlsverify
+ // If tlsBook is true or undefined, we do not skip
+ SkipTlsVerify := false
+ if tlsBool == types.OptionalBoolFalse {
+ SkipTlsVerify = true
+ }
+ imageID, err := iopodman.PullImage().Call(r.Conn, name, dockeroptions.DockerCertPath, "", signaturePolicyPath, SkipTlsVerify)
+ if err != nil {
+ return nil, err
+ }
+ newImage, err := r.NewImageFromLocal(imageID)
+ if err != nil {
+ return nil, err
+ }
+ return newImage, nil
+}
+
func splitStringDate(d string) (time.Time, error) {
fields := strings.Fields(d)
t := fmt.Sprintf("%sT%sZ", fields[0], fields[1])
@@ -173,3 +233,78 @@ func (ci *ContainerImage) Labels(ctx context.Context) (map[string]string, error)
func (ci *ContainerImage) Dangling() bool {
return len(ci.Names()) == 0
}
+
+// TagImage ...
+func (ci *ContainerImage) TagImage(tag string) error {
+ _, err := iopodman.TagImage().Call(ci.Runtime.Conn, ci.ID(), tag)
+ return err
+}
+
+// RemoveImage calls varlink to remove an image
+func (r *LocalRuntime) RemoveImage(ctx context.Context, img *ContainerImage, force bool) (string, error) {
+ return iopodman.RemoveImage().Call(r.Conn, img.InputName, force)
+}
+
+// History returns the history of an image and its layers
+func (ci *ContainerImage) History(ctx context.Context) ([]*image.History, error) {
+ var imageHistories []*image.History
+
+ reply, err := iopodman.HistoryImage().Call(ci.Runtime.Conn, ci.InputName)
+ if err != nil {
+ return nil, err
+ }
+ for _, h := range reply {
+ created, err := splitStringDate(h.Created)
+ if err != nil {
+ return nil, err
+ }
+ ih := image.History{
+ ID: h.Id,
+ Created: &created,
+ CreatedBy: h.CreatedBy,
+ Size: h.Size,
+ Comment: h.Comment,
+ }
+ imageHistories = append(imageHistories, &ih)
+ }
+ return imageHistories, nil
+}
+
+// LookupContainer gets basic information about container over a varlink
+// connection and then translates it to a *Container
+func (r *RemoteRuntime) LookupContainer(idOrName string) (*Container, error) {
+ container, err := iopodman.GetContainer().Call(r.Conn, idOrName)
+ if err != nil {
+ return nil, err
+ }
+ return listContainerDataToContainer(container)
+}
+
+// listContainerDataToContainer takes a varlink listcontainerData struct and makes
+// an "adapted" Container
+func listContainerDataToContainer(listData iopodman.ListContainerData) (*Container, error) {
+ created, err := splitStringDate(listData.Createdat)
+ if err != nil {
+ return nil, err
+ }
+ rc := remoteContainer{
+ // TODO commented out attributes will be populated when podman-remote ps
+ // is implemented. They are not needed yet for basic container operations.
+ ID: listData.Id,
+ Image: listData.Image,
+ ImageID: listData.Imageid,
+ Command: listData.Command,
+ Created: created,
+ RunningFor: listData.Runningfor,
+ Status: listData.Status,
+ //ports:
+ RootFsSize: listData.Rootfssize,
+ RWSize: listData.Rwsize,
+ Names: listData.Names,
+ //Labels:
+ //Mounts
+ //ContainerRunning:
+ //namespaces:
+ }
+ return &Container{rc}, nil
+}
diff --git a/libpod/container.go b/libpod/container.go
index ca83bbffe..95f7a2972 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -350,6 +350,9 @@ type ContainerConfig struct {
PostConfigureNetNS bool `json:"postConfigureNetNS"`
+ // OCIRuntime used to create the container
+ OCIRuntime string `json:"runtime,omitempty"`
+
// ExitCommand is the container's exit command.
// This Command will be executed when the container exits
ExitCommand []string `json:"exitCommand,omitempty"`
diff --git a/libpod/container_api.go b/libpod/container_api.go
index 4eaf737b0..149867759 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -3,6 +3,7 @@ package libpod
import (
"context"
"fmt"
+ "io"
"io/ioutil"
"os"
"strconv"
@@ -413,6 +414,25 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir
return waitErr
}
+// AttachStreams contains streams that will be attached to the container
+type AttachStreams struct {
+ // OutputStream will be attached to container's STDOUT
+ OutputStream io.WriteCloser
+ // ErrorStream will be attached to container's STDERR
+ ErrorStream io.WriteCloser
+ // InputStream will be attached to container's STDIN
+ InputStream io.Reader
+ // AttachOutput is whether to attach to STDOUT
+ // If false, stdout will not be attached
+ AttachOutput bool
+ // AttachError is whether to attach to STDERR
+ // If false, stdout will not be attached
+ AttachError bool
+ // AttachInput is whether to attach to STDIN
+ // If false, stdout will not be attached
+ AttachInput bool
+}
+
// Attach attaches to a container
func (c *Container) Attach(streams *AttachStreams, keys string, resize <-chan remotecommand.TerminalSize) error {
if !c.batched {
diff --git a/libpod/container_attach.go b/libpod/container_attach_linux.go
index f925c3897..1d6f0bd96 100644
--- a/libpod/container_attach.go
+++ b/libpod/container_attach_linux.go
@@ -1,3 +1,5 @@
+//+build linux
+
package libpod
import (
@@ -27,25 +29,6 @@ const (
AttachPipeStderr = 3
)
-// AttachStreams contains streams that will be attached to the container
-type AttachStreams struct {
- // OutputStream will be attached to container's STDOUT
- OutputStream io.WriteCloser
- // ErrorStream will be attached to container's STDERR
- ErrorStream io.WriteCloser
- // InputStream will be attached to container's STDIN
- InputStream io.Reader
- // AttachOutput is whether to attach to STDOUT
- // If false, stdout will not be attached
- AttachOutput bool
- // AttachError is whether to attach to STDERR
- // If false, stdout will not be attached
- AttachError bool
- // AttachInput is whether to attach to STDIN
- // If false, stdout will not be attached
- AttachInput bool
-}
-
// Attach to the given container
// Does not check if state is appropriate
func (c *Container) attach(streams *AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, startContainer bool) error {
diff --git a/libpod/container_attach_unsupported.go b/libpod/container_attach_unsupported.go
new file mode 100644
index 000000000..068652b29
--- /dev/null
+++ b/libpod/container_attach_unsupported.go
@@ -0,0 +1,11 @@
+//+build !linux
+
+package libpod
+
+import (
+ "k8s.io/client-go/tools/remotecommand"
+)
+
+func (c *Container) attach(streams *AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, startContainer bool) error {
+ return ErrNotImplemented
+}
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 90f4659da..ce8791f08 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -540,7 +540,7 @@ func (c *Container) isStopped() (bool, error) {
if err != nil {
return true, err
}
- return (c.state.State == ContainerStateStopped || c.state.State == ContainerStateExited), nil
+ return (c.state.State != ContainerStateRunning && c.state.State != ContainerStatePaused), nil
}
// save container state to the database
diff --git a/libpod/image/image.go b/libpod/image/image.go
index dda753385..ea326d820 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -25,7 +25,7 @@ import (
"github.com/containers/libpod/pkg/util"
"github.com/containers/storage"
"github.com/containers/storage/pkg/reexec"
- "github.com/opencontainers/go-digest"
+ digest "github.com/opencontainers/go-digest"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -226,7 +226,6 @@ func (i *Image) getLocalImage() (*storage.Image, error) {
i.InputName = dest.DockerReference().String()
}
- var taggedName string
img, err := i.imageruntime.getImage(stripSha256(i.InputName))
if err == nil {
return img.image, err
@@ -240,25 +239,18 @@ func (i *Image) getLocalImage() (*storage.Image, error) {
return nil, err
}
- // the inputname isn't tagged, so we assume latest and try again
- if !decomposedImage.isTagged {
- taggedName = fmt.Sprintf("%s:latest", i.InputName)
- img, err = i.imageruntime.getImage(taggedName)
- if err == nil {
- return img.image, nil
- }
- }
-
// The image has a registry name in it and we made sure we looked for it locally
// with a tag. It cannot be local.
if decomposedImage.hasRegistry {
return nil, errors.Wrapf(ErrNoSuchImage, imageError)
-
}
-
// if the image is saved with the repository localhost, searching with localhost prepended is necessary
// We don't need to strip the sha because we have already determined it is not an ID
- img, err = i.imageruntime.getImage(fmt.Sprintf("%s/%s", DefaultLocalRegistry, i.InputName))
+ ref, err := decomposedImage.referenceWithRegistry(DefaultLocalRegistry)
+ if err != nil {
+ return nil, err
+ }
+ img, err = i.imageruntime.getImage(ref.String())
if err == nil {
return img.image, err
}
@@ -452,35 +444,42 @@ func getImageDigest(ctx context.Context, src types.ImageReference, sc *types.Sys
return "@" + digest.Hex(), nil
}
-// normalizeTag returns the canonical version of tag for use in Image.Names()
-func normalizeTag(tag string) (string, error) {
+// 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 "", err
- }
- // If the input does not have a tag, we need to add one (latest)
- if !decomposedTag.isTagged {
- tag = fmt.Sprintf("%s:%s", tag, decomposedTag.Tag)
+ return nil, err
}
// If the input doesn't specify a registry, set the registry to localhost
+ var ref reference.Named
if !decomposedTag.hasRegistry {
- tag = fmt.Sprintf("%s/%s", DefaultLocalRegistry, tag)
+ ref, err = decomposedTag.referenceWithRegistry(DefaultLocalRegistry)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ ref, err = decomposedTag.normalizedReference()
+ if err != nil {
+ return nil, err
+ }
}
- return tag, nil
+ // 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 {
i.reloadImage()
- tag, err := normalizeTag(tag)
+ ref, err := normalizedTag(tag)
if err != nil {
return err
}
tags := i.Names()
- if util.StringInSlice(tag, tags) {
+ if util.StringInSlice(ref.String(), tags) {
return nil
}
- tags = append(tags, tag)
+ tags = append(tags, ref.String())
if err := i.imageruntime.store.SetNames(i.ID(), tags); err != nil {
return err
}
@@ -931,21 +930,23 @@ func (i *Image) MatchRepoTag(input string) (string, error) {
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
}
- if dcRepoName.Registry == dcImage.Registry && dcImage.Registry != "" {
+ repoNameRegistry, repoNameName, repoNameSuspiciousTagValueForSearch := dcRepoName.suspiciousRefNameTagValuesForSearch()
+ if repoNameRegistry == imageRegistry && imageRegistry != "" {
count++
}
- if dcRepoName.name == dcImage.name && dcImage.name != "" {
+ if repoNameName == imageName && imageName != "" {
count++
- } else if splitString(dcRepoName.name) == splitString(dcImage.name) {
+ } else if splitString(repoNameName) == splitString(imageName) {
count++
}
- if dcRepoName.Tag == dcImage.Tag {
+ if repoNameSuspiciousTagValueForSearch == imageSuspiciousTagValueForSearch {
count++
}
results[count] = append(results[count], repoName)
diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go
index 2a68fd273..077ae460e 100644
--- a/libpod/image/image_test.go
+++ b/libpod/image/image_test.go
@@ -257,24 +257,25 @@ func Test_stripSha256(t *testing.T) {
assert.Equal(t, stripSha256("sha256:a"), "a")
}
-func TestNormalizeTag(t *testing.T) {
+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 + ":none"}, // Qualified name@digest; FIXME: The result is not even syntactically valid!
+ {"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 := normalizeTag(c.input)
+ 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)
+ assert.Equal(t, c.expected, res.String())
}
}
}
diff --git a/libpod/image/parts.go b/libpod/image/parts.go
index b2a69f26c..dfdf0b08a 100644
--- a/libpod/image/parts.go
+++ b/libpod/image/parts.go
@@ -1,23 +1,20 @@
package image
import (
- "fmt"
"strings"
"github.com/containers/image/docker/reference"
+ "github.com/pkg/errors"
)
-// Parts describes the parts of an image's name
-type Parts struct {
- transport string
- Registry string
- name string
- Tag string
- isTagged bool
- hasRegistry bool
+// 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
+// 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"
}
@@ -30,68 +27,78 @@ func GetImageBaseName(input string) (string, error) {
if err != nil {
return "", err
}
- splitImageName := strings.Split(decomposedImage.name, "/")
+ splitImageName := strings.Split(decomposedImage.unnormalizedRef.Name(), "/")
return splitImageName[len(splitImageName)-1], nil
}
-// DecomposeString decomposes a string name into imageParts description. This
-// is a wrapper for decompose
-func DecomposeString(input string) (Parts, error) {
- return decompose(input)
-}
-
// decompose breaks an input name into an imageParts description
-func decompose(input string) (Parts, error) {
- var (
- parts Parts
- hasRegistry bool
- tag string
- )
+func decompose(input string) (imageParts, error) {
imgRef, err := reference.Parse(input)
if err != nil {
- return parts, err
+ return imageParts{}, err
}
- ntag, isTagged := imgRef.(reference.NamedTagged)
- if !isTagged {
- tag = "latest"
- if _, hasDigest := imgRef.(reference.Digested); hasDigest {
- tag = "none"
- }
- } else {
- tag = ntag.Tag()
+ 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,
+// gespecially 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 = ""
}
- registry := reference.Domain(imgRef.(reference.Named))
- imageName := reference.Path(imgRef.(reference.Named))
- // Is this a Registry or a repo?
- if isRegistry(registry) {
- hasRegistry = true
+
+ 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 {
- if registry != "" {
- imageName = registry + "/" + imageName
- registry = ""
- }
+ tag = "latest"
}
- return Parts{
- Registry: registry,
- hasRegistry: hasRegistry,
- name: imageName,
- Tag: tag,
- isTagged: isTagged,
- transport: DefaultTransport,
- }, nil
+ return registry, imageName, tag
}
-// assemble concatenates an image's parts into a string
-func (ip *Parts) assemble() string {
- spec := fmt.Sprintf("%s:%s", ip.name, ip.Tag)
-
- if ip.Registry != "" {
- spec = fmt.Sprintf("%s/%s", ip.Registry, spec)
+// 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)
}
- return spec
+ // 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
}
-// assemble concatenates an image's parts with transport into a string
-func (ip *Parts) assembleWithTransport() string {
- return fmt.Sprintf("%s%s", ip.transport, ip.assemble())
+// 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
index 1e01c85d3..726e55e86 100644
--- a/libpod/image/parts_test.go
+++ b/libpod/image/parts_test.go
@@ -4,64 +4,120 @@ 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
- transport, registry, name, tag string
- isTagged, hasRegistry bool
- assembled string
- assembledWithTransport string
+ input string
+ registry, name, suspiciousTagValueForSearch string
+ hasRegistry bool
}{
- {"#", "", "", "", "", false, false, "", ""}, // Entirely invalid input
+ {"#", "", "", "", false}, // Entirely invalid input
{ // Fully qualified docker.io, name-only input
- "docker.io/library/busybox", "docker://", "docker.io", "library/busybox", "latest", false, true,
- "docker.io/library/busybox:latest", "docker://docker.io/library/busybox:latest",
+ "docker.io/library/busybox", "docker.io", "library/busybox", "latest", true,
},
{ // Fully qualified example.com, name-only input
- "example.com/ns/busybox", "docker://", "example.com", "ns/busybox", "latest", false, true,
- "example.com/ns/busybox:latest", "docker://example.com/ns/busybox:latest",
+ "example.com/ns/busybox", "example.com", "ns/busybox", "latest", true,
},
{ // Unqualified single-name input
- "busybox", "docker://", "", "busybox", "latest", false, false,
- "busybox:latest", "docker://busybox:latest",
+ "busybox", "", "busybox", "latest", false,
},
{ // Unqualified namespaced input
- "ns/busybox", "docker://", "", "ns/busybox", "latest", false, false,
- "ns/busybox:latest", "docker://ns/busybox:latest",
+ "ns/busybox", "", "ns/busybox", "latest", false,
},
{ // name:tag
- "example.com/ns/busybox:notlatest", "docker://", "example.com", "ns/busybox", "notlatest", true, true,
- "example.com/ns/busybox:notlatest", "docker://example.com/ns/busybox:notlatest",
+ "example.com/ns/busybox:notlatest", "example.com", "ns/busybox", "notlatest", true,
},
{ // name@digest
- // FIXME? .tag == "none"
- "example.com/ns/busybox" + digestSuffix, "docker://", "example.com", "ns/busybox", "none", false, true,
- // FIXME: this drops the digest and replaces it with an incorrect tag.
- "example.com/ns/busybox:none", "docker://example.com/ns/busybox:none",
+ // FIXME? .suspiciousTagValueForSearch == "none"
+ "example.com/ns/busybox" + digestSuffix, "example.com", "ns/busybox", "none", true,
},
{ // name:tag@digest
- "example.com/ns/busybox:notlatest" + digestSuffix, "docker://", "example.com", "ns/busybox", "notlatest", true, true,
- // FIXME: This drops the digest
- "example.com/ns/busybox:notlatest", "docker://example.com/ns/busybox:notlatest",
+ "example.com/ns/busybox:notlatest" + digestSuffix, "example.com", "ns/busybox", "notlatest", true,
},
} {
parts, err := decompose(c.input)
- if c.transport == "" {
+ if c.name == "" {
assert.Error(t, err, c.input)
} else {
assert.NoError(t, err, c.input)
- assert.Equal(t, c.transport, parts.transport, c.input)
- assert.Equal(t, c.registry, parts.Registry, c.input)
- assert.Equal(t, c.name, parts.name, c.input)
- assert.Equal(t, c.tag, parts.Tag, c.input)
- assert.Equal(t, c.isTagged, parts.isTagged, 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)
- assert.Equal(t, c.assembled, parts.assemble(), c.input)
- assert.Equal(t, c.assembledWithTransport, parts.assembleWithTransport(), 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/pull.go b/libpod/image/pull.go
index 203e94310..434b83520 100644
--- a/libpod/image/pull.go
+++ b/libpod/image/pull.go
@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"io"
- "strings"
cp "github.com/containers/image/copy"
"github.com/containers/image/directory"
@@ -76,9 +75,11 @@ func (ir *Runtime) getPullRefPair(srcRef types.ImageReference, destName string)
decomposedDest, err := decompose(destName)
if err == nil && !decomposedDest.hasRegistry {
// If the image doesn't have a registry, set it as the default repo
- decomposedDest.Registry = DefaultLocalRegistry
- decomposedDest.hasRegistry = true
- destName = decomposedDest.assemble()
+ ref, err := decomposedDest.referenceWithRegistry(DefaultLocalRegistry)
+ if err != nil {
+ return pullRefPair{}, err
+ }
+ destName = ref.String()
}
reference := destName
@@ -270,12 +271,6 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa
return images, nil
}
-// hasShaInInputName returns a bool as to whether the user provided an image name that includes
-// a reference to a specific sha
-func hasShaInInputName(inputName string) bool {
- return strings.Contains(inputName, "@sha256:")
-}
-
// 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(inputName string) (*pullGoal, error) {
@@ -284,31 +279,11 @@ func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(inputName string) (*pullG
return nil, err
}
if decomposedImage.hasRegistry {
- var imageName, destName string
- if hasShaInInputName(inputName) {
- imageName = fmt.Sprintf("%s%s", decomposedImage.transport, inputName)
- } else {
- imageName = decomposedImage.assembleWithTransport()
- }
- srcRef, err := alltransports.ParseImageName(imageName)
+ srcRef, err := docker.ParseReference("//" + inputName)
if err != nil {
return nil, errors.Wrapf(err, "unable to parse '%s'", inputName)
}
- if hasShaInInputName(inputName) {
- destName = decomposedImage.assemble()
- } else {
- destName = inputName
- }
- destRef, err := is.Transport.ParseStoreReference(ir.store, destName)
- if err != nil {
- return nil, errors.Wrapf(err, "error parsing dest reference name %#v", destName)
- }
- ps := pullRefPair{
- image: inputName,
- srcRef: srcRef,
- dstRef: destRef,
- }
- return singlePullRefPairGoal(ps), nil
+ return ir.getSinglePullRefPairGoal(srcRef, inputName)
}
searchRegistries, err := registries.GetRegistries()
@@ -317,22 +292,18 @@ func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(inputName string) (*pullG
}
var refPairs []pullRefPair
for _, registry := range searchRegistries {
- decomposedImage.Registry = registry
- imageName := decomposedImage.assembleWithTransport()
- if hasShaInInputName(inputName) {
- imageName = fmt.Sprintf("%s%s/%s", decomposedImage.transport, registry, inputName)
- }
- srcRef, err := alltransports.ParseImageName(imageName)
+ ref, err := decomposedImage.referenceWithRegistry(registry)
if err != nil {
- return nil, errors.Wrapf(err, "unable to parse '%s'", inputName)
+ return nil, err
}
- ps := pullRefPair{
- image: decomposedImage.assemble(),
- srcRef: srcRef,
+ imageName := ref.String()
+ srcRef, err := docker.ParseReference("//" + imageName)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to parse '%s'", imageName)
}
- ps.dstRef, err = is.Transport.ParseStoreReference(ir.store, ps.image)
+ ps, err := ir.getPullRefPair(srcRef, imageName)
if err != nil {
- return nil, errors.Wrapf(err, "error parsing dest reference name %#v", ps.image)
+ return nil, err
}
refPairs = append(refPairs, ps)
}
diff --git a/libpod/image/pull_test.go b/libpod/image/pull_test.go
index 37b45dc83..3890c5e6c 100644
--- a/libpod/image/pull_test.go
+++ b/libpod/image/pull_test.go
@@ -76,9 +76,7 @@ func TestGetPullRefPair(t *testing.T) {
},
{ // name, no registry, no tag:
"dir:/dev/this-does-not-exist", "from-directory",
- // FIXME(?) Adding a registry also adds a :latest tag. OTOH that actually matches the used destination.
- // Either way it is surprising that the localhost/ addition changes this. (mitr hoping to remove the "image" member).
- "localhost/from-directory:latest", "localhost/from-directory:latest",
+ "localhost/from-directory", "localhost/from-directory:latest",
},
{ // registry/name:tag :
"dir:/dev/this-does-not-exist", "example.com/from-directory:notlatest",
@@ -90,8 +88,7 @@ func TestGetPullRefPair(t *testing.T) {
},
{ // name@digest, no registry:
"dir:/dev/this-does-not-exist", "from-directory" + digestSuffix,
- // FIXME?! Why is this dropping the digest, and adding :none?!
- "localhost/from-directory:none", "localhost/from-directory:none",
+ "localhost/from-directory" + digestSuffix, "localhost/from-directory" + digestSuffix,
},
{ // registry/name@digest:
"dir:/dev/this-does-not-exist", "example.com/from-directory" + digestSuffix,
@@ -211,14 +208,13 @@ func TestPullGoalFromImageReference(t *testing.T) {
false,
},
{ // Relative path, single element.
- // FIXME? Note the :latest difference in .image.
"dir:this-does-not-exist",
- []expected{{"localhost/this-does-not-exist:latest", "localhost/this-does-not-exist:latest"}},
+ []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:latest", "localhost/testdata/this-does-not-exist:latest"}},
+ []expected{{"localhost/testdata/this-does-not-exist", "localhost/testdata/this-does-not-exist:latest"}},
false,
},
@@ -324,8 +320,7 @@ func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) {
{ // Qualified example.com, name@digest.
"example.com/ns/busybox" + digestSuffix,
[]pullRefStrings{{"example.com/ns/busybox" + digestSuffix, "docker://example.com/ns/busybox" + digestSuffix,
- // FIXME?! Why is .dstName dropping the digest, and adding :none?!
- "example.com/ns/busybox:none"}},
+ "example.com/ns/busybox" + digestSuffix}},
false,
},
// Qualified example.com, name:tag@digest. This code is happy to try, but .srcRef parsing currently rejects such input.
@@ -333,16 +328,16 @@ func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) {
{ // Unqualified, single-name, name-only
"busybox",
[]pullRefStrings{
- {"example.com/busybox:latest", "docker://example.com/busybox:latest", "example.com/busybox:latest"},
+ {"example.com/busybox", "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/busybox:latest", "docker://busybox:latest", "docker.io/library/busybox:latest"},
+ {"docker.io/library/busybox", "docker://busybox:latest", "docker.io/library/busybox:latest"},
},
true,
},
{ // Unqualified, namespaced, name-only
"ns/busybox",
[]pullRefStrings{
- {"example.com/ns/busybox:latest", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"},
+ {"example.com/ns/busybox", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"},
},
true,
},
@@ -351,17 +346,16 @@ func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) {
[]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/busybox:notlatest", "docker://busybox:notlatest", "docker.io/library/busybox:notlatest"},
+ {"docker.io/library/busybox:notlatest", "docker://busybox:notlatest", "docker.io/library/busybox:notlatest"},
},
true,
},
{ // Unqualified, name@digest
"busybox" + digestSuffix,
[]pullRefStrings{
- // FIXME?! Why is .input and .dstName dropping the digest, and adding :none?!
- {"example.com/busybox:none", "docker://example.com/busybox" + digestSuffix, "example.com/busybox:none"},
+ {"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/busybox:none", "docker://busybox" + digestSuffix, "docker.io/library/busybox:none"},
+ {"docker.io/library/busybox" + digestSuffix, "docker://busybox" + digestSuffix, "docker.io/library/busybox" + digestSuffix},
},
true,
},
diff --git a/libpod/image/utils.go b/libpod/image/utils.go
index 135b47008..ad027f32a 100644
--- a/libpod/image/utils.go
+++ b/libpod/image/utils.go
@@ -16,7 +16,8 @@ import (
// findImageInRepotags takes an imageParts struct and searches images' repotags for
// a match on name:tag
-func findImageInRepotags(search Parts, images []*Image) (*storage.Image, error) {
+func findImageInRepotags(search imageParts, images []*Image) (*storage.Image, error) {
+ _, searchName, searchSuspiciousTagValueForSearch := search.suspiciousRefNameTagValuesForSearch()
var results []*storage.Image
for _, image := range images {
for _, name := range image.Names() {
@@ -25,21 +26,22 @@ func findImageInRepotags(search Parts, images []*Image) (*storage.Image, error)
if err != nil {
continue
}
- if d.name == search.name && d.Tag == search.Tag {
+ _, dName, dSuspiciousTagValueForSearch := d.suspiciousRefNameTagValuesForSearch()
+ if dName == searchName && dSuspiciousTagValueForSearch == searchSuspiciousTagValueForSearch {
results = append(results, image.image)
continue
}
// account for registry:/somedir/image
- if strings.HasSuffix(d.name, search.name) && d.Tag == search.Tag {
+ if strings.HasSuffix(dName, searchName) && dSuspiciousTagValueForSearch == searchSuspiciousTagValueForSearch {
results = append(results, image.image)
continue
}
}
}
if len(results) == 0 {
- return &storage.Image{}, errors.Errorf("unable to find a name and tag match for %s in repotags", search.name)
+ return &storage.Image{}, errors.Errorf("unable to find a name and tag match for %s in repotags", searchName)
} else if len(results) > 1 {
- return &storage.Image{}, errors.Errorf("found multiple name and tag matches for %s in repotags", search.name)
+ return &storage.Image{}, errors.Errorf("found multiple name and tag matches for %s in repotags", searchName)
}
return results[0], nil
}
diff --git a/libpod/info.go b/libpod/info.go
index a98f93897..191ce6810 100644
--- a/libpod/info.go
+++ b/libpod/info.go
@@ -4,7 +4,6 @@ import (
"bufio"
"bytes"
"fmt"
- "github.com/containers/buildah"
"io/ioutil"
"os"
"runtime"
@@ -12,6 +11,7 @@ import (
"strings"
"time"
+ "github.com/containers/buildah"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/util"
"github.com/containers/libpod/utils"
@@ -184,12 +184,12 @@ func (r *Runtime) GetConmonVersion() (string, error) {
// GetOCIRuntimePath returns the path to the OCI Runtime Path the runtime is using
func (r *Runtime) GetOCIRuntimePath() string {
- return r.ociRuntimePath
+ return r.ociRuntimePath.Paths[0]
}
// GetOCIRuntimeVersion returns a string representation of the oci runtimes version
func (r *Runtime) GetOCIRuntimeVersion() (string, error) {
- output, err := utils.ExecCmd(r.ociRuntimePath, "--version")
+ output, err := utils.ExecCmd(r.ociRuntimePath.Paths[0], "--version")
if err != nil {
return "", err
}
diff --git a/libpod/lock/shm_lock_manager_unsupported.go b/libpod/lock/shm_lock_manager_unsupported.go
index a1340fcd1..cbdb2f7bc 100644
--- a/libpod/lock/shm_lock_manager_unsupported.go
+++ b/libpod/lock/shm_lock_manager_unsupported.go
@@ -9,12 +9,12 @@ import "fmt"
type SHMLockManager struct{}
// NewSHMLockManager is not supported on this platform
-func NewSHMLockManager(numLocks uint32) (Manager, error) {
+func NewSHMLockManager(path string, numLocks uint32) (Manager, error) {
return nil, fmt.Errorf("not supported")
}
// OpenSHMLockManager is not supported on this platform
-func OpenSHMLockManager(numLocks uint32) (Manager, error) {
+func OpenSHMLockManager(path string, numLocks uint32) (Manager, error) {
return nil, fmt.Errorf("not supported")
}
diff --git a/libpod/oci.go b/libpod/oci.go
index 7a908db2e..4092657f8 100644
--- a/libpod/oci.go
+++ b/libpod/oci.go
@@ -75,10 +75,10 @@ type syncInfo struct {
}
// Make a new OCI runtime with provided options
-func newOCIRuntime(name string, path string, conmonPath string, conmonEnv []string, cgroupManager string, tmpDir string, logSizeMax int64, noPivotRoot bool, reservePorts bool) (*OCIRuntime, error) {
+func newOCIRuntime(oruntime OCIRuntimePath, conmonPath string, conmonEnv []string, cgroupManager string, tmpDir string, logSizeMax int64, noPivotRoot bool, reservePorts bool) (*OCIRuntime, error) {
runtime := new(OCIRuntime)
- runtime.name = name
- runtime.path = path
+ runtime.name = oruntime.Name
+ runtime.path = oruntime.Paths[0]
runtime.conmonPath = conmonPath
runtime.conmonEnv = conmonEnv
runtime.cgroupManager = cgroupManager
diff --git a/libpod/options.go b/libpod/options.go
index 319e1f6c6..d965c058e 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -137,17 +137,17 @@ func WithStateType(storeType RuntimeStateStore) RuntimeOption {
}
// WithOCIRuntime specifies an OCI runtime to use for running containers.
-func WithOCIRuntime(runtimePath string) RuntimeOption {
+func WithOCIRuntime(runtime string) RuntimeOption {
return func(rt *Runtime) error {
if rt.valid {
return ErrRuntimeFinalized
}
- if runtimePath == "" {
+ if runtime == "" {
return errors.Wrapf(ErrInvalidArg, "must provide a valid path")
}
- rt.config.RuntimePath = []string{runtimePath}
+ rt.config.OCIRuntime = runtime
return nil
}
diff --git a/libpod/runtime.go b/libpod/runtime.go
index c9471247c..5ff8b30f6 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -86,7 +86,7 @@ type Runtime struct {
imageContext *types.SystemContext
ociRuntime *OCIRuntime
netPlugin ocicni.CNIPlugin
- ociRuntimePath string
+ ociRuntimePath OCIRuntimePath
conmonPath string
valid bool
lock sync.RWMutex
@@ -96,6 +96,14 @@ type Runtime struct {
configuredFrom *runtimeConfiguredFrom
}
+// OCIRuntimePath contains information about an OCI runtime.
+type OCIRuntimePath struct {
+ // Name of the runtime to refer to by the --runtime flag
+ Name string `toml:"name"`
+ // Paths to check for this executable
+ Paths []string `toml:"paths"`
+}
+
// RuntimeConfig contains configuration options used to set up the runtime
type RuntimeConfig struct {
// StorageConfig is the configuration used by containers/storage
@@ -118,10 +126,10 @@ type RuntimeConfig struct {
// cause conflicts in containers/storage
// As such this is not exposed via the config file
StateType RuntimeStateStore `toml:"-"`
- // RuntimePath is the path to OCI runtime binary for launching
- // containers
- // The first path pointing to a valid file will be used
- RuntimePath []string `toml:"runtime_path"`
+ // OCIRuntime is the OCI runtime to use.
+ OCIRuntime string `toml:"runtime"`
+ // OCIRuntimes are the set of configured OCI runtimes (default is runc)
+ OCIRuntimes map[string][]string `toml:"runtimes"`
// ConmonPath is the path to the Conmon binary used for managing
// containers
// The first path pointing to a valid file will be used
@@ -213,14 +221,17 @@ var (
StorageConfig: storage.StoreOptions{},
ImageDefaultTransport: DefaultTransport,
StateType: BoltDBStateStore,
- RuntimePath: []string{
- "/usr/bin/runc",
- "/usr/sbin/runc",
- "/usr/local/bin/runc",
- "/usr/local/sbin/runc",
- "/sbin/runc",
- "/bin/runc",
- "/usr/lib/cri-o-runc/sbin/runc",
+ OCIRuntime: "runc",
+ OCIRuntimes: map[string][]string{
+ "runc": {
+ "/usr/bin/runc",
+ "/usr/sbin/runc",
+ "/usr/local/bin/runc",
+ "/usr/local/sbin/runc",
+ "/sbin/runc",
+ "/bin/runc",
+ "/usr/lib/cri-o-runc/sbin/runc",
+ },
},
ConmonPath: []string{
"/usr/libexec/podman/conmon",
@@ -414,8 +425,9 @@ func NewRuntimeFromConfig(configPath string, options ...RuntimeOption) (runtime
runtime.config = new(RuntimeConfig)
runtime.configuredFrom = new(runtimeConfiguredFrom)
- // Set two fields not in the TOML config
+ // Set three fields not in the TOML config
runtime.config.StateType = defaultRuntimeConfig.StateType
+ runtime.config.OCIRuntime = defaultRuntimeConfig.OCIRuntime
runtime.config.StorageConfig = storage.StoreOptions{}
// Check to see if the given configuration file exists
@@ -453,22 +465,35 @@ func NewRuntimeFromConfig(configPath string, options ...RuntimeOption) (runtime
func makeRuntime(runtime *Runtime) (err error) {
// Find a working OCI runtime binary
foundRuntime := false
- for _, path := range runtime.config.RuntimePath {
- stat, err := os.Stat(path)
- if err != nil {
- continue
- }
- if stat.IsDir() {
- continue
- }
+ // If runtime is an absolute path, then use it as it is.
+ if runtime.config.OCIRuntime[0] == '/' {
foundRuntime = true
- runtime.ociRuntimePath = path
- break
+ runtime.ociRuntimePath = OCIRuntimePath{Name: filepath.Base(runtime.config.OCIRuntime), Paths: []string{runtime.config.OCIRuntime}}
+ } else {
+ // If not, look it up in the configuration.
+ paths := runtime.config.OCIRuntimes[runtime.config.OCIRuntime]
+ if paths != nil {
+ for _, path := range paths {
+ stat, err := os.Stat(path)
+ if err != nil {
+ if os.IsNotExist(err) {
+ continue
+ }
+ return errors.Wrapf(err, "cannot stat %s", path)
+ }
+ if !stat.Mode().IsRegular() {
+ continue
+ }
+ foundRuntime = true
+ runtime.ociRuntimePath = OCIRuntimePath{Name: runtime.config.OCIRuntime, Paths: []string{path}}
+ break
+ }
+ }
}
if !foundRuntime {
return errors.Wrapf(ErrInvalidArg,
"could not find a working binary (configured options: %v)",
- runtime.config.RuntimePath)
+ runtime.config.OCIRuntimes)
}
// Find a working conmon binary
@@ -619,7 +644,7 @@ func makeRuntime(runtime *Runtime) (err error) {
}
// Make an OCI runtime to perform container operations
- ociRuntime, err := newOCIRuntime("runc", runtime.ociRuntimePath,
+ ociRuntime, err := newOCIRuntime(runtime.ociRuntimePath,
runtime.conmonPath, runtime.config.ConmonEnvVars,
runtime.config.CgroupManager, runtime.config.TmpDir,
runtime.config.MaxLogSize, runtime.config.NoPivotRoot,
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index ab79fe5fb..68599fe6d 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -62,6 +62,8 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options ..
ctr.config.StopTimeout = CtrRemoveTimeout
+ ctr.config.OCIRuntime = r.config.OCIRuntime
+
// Set namespace based on current runtime namespace
// Do so before options run so they can override it
if r.config.Namespace != "" {
diff --git a/libpod/runtime_volume_unsupported.go b/libpod/runtime_volume_unsupported.go
new file mode 100644
index 000000000..d87459759
--- /dev/null
+++ b/libpod/runtime_volume_unsupported.go
@@ -0,0 +1,19 @@
+// +build !linux
+
+package libpod
+
+import (
+ "context"
+)
+
+func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force, prune bool) error {
+ return ErrNotImplemented
+}
+
+func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption) (*Volume, error) {
+ return nil, ErrNotImplemented
+}
+
+func (r *Runtime) NewVolume(ctx context.Context, options ...VolumeCreateOption) (*Volume, error) {
+ return nil, ErrNotImplemented
+}