summaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
Diffstat (limited to 'libpod')
-rw-r--r--libpod/container_api.go5
-rw-r--r--libpod/container_graph.go24
-rw-r--r--libpod/container_graph_test.go26
-rw-r--r--libpod/container_internal.go2
-rw-r--r--libpod/image/image.go29
-rw-r--r--libpod/image/image_test.go11
-rw-r--r--libpod/image/pull.go25
-rw-r--r--libpod/networking_linux.go27
-rw-r--r--libpod/oci.go14
-rw-r--r--libpod/oci_internal_linux.go28
-rw-r--r--libpod/oci_linux.go6
-rw-r--r--libpod/options.go71
-rw-r--r--libpod/pod.go2
-rw-r--r--libpod/pod_api.go4
-rw-r--r--libpod/runtime.go23
-rw-r--r--libpod/runtime_pod_infra_linux.go6
-rw-r--r--libpod/runtime_pod_linux.go10
-rw-r--r--libpod/runtime_volume_linux.go7
-rw-r--r--libpod/volume.go46
-rw-r--r--libpod/volume_inspect.go70
20 files changed, 296 insertions, 140 deletions
diff --git a/libpod/container_api.go b/libpod/container_api.go
index abcfcb271..9e59104cc 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -274,6 +274,11 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir
}
}()
+ // if the user is empty, we should inherit the user that the container is currently running with
+ if user == "" {
+ user = c.config.User
+ }
+
pid, attachChan, err := c.ociRuntime.execContainer(c, cmd, capList, env, tty, workDir, user, sessionID, streams, preserveFDs, resize, detachKeys)
if err != nil {
ec := define.ExecErrorCodeGeneric
diff --git a/libpod/container_graph.go b/libpod/container_graph.go
index 5aa51bc2f..f6988e1ac 100644
--- a/libpod/container_graph.go
+++ b/libpod/container_graph.go
@@ -16,14 +16,30 @@ type containerNode struct {
dependedOn []*containerNode
}
-type containerGraph struct {
+// ContainerGraph is a dependency graph based on a set of containers.
+type ContainerGraph struct {
nodes map[string]*containerNode
noDepNodes []*containerNode
notDependedOnNodes map[string]*containerNode
}
-func buildContainerGraph(ctrs []*Container) (*containerGraph, error) {
- graph := new(containerGraph)
+// DependencyMap returns the dependency graph as map with the key being a
+// container and the value being the containers the key depends on.
+func (cg *ContainerGraph) DependencyMap() (dependencies map[*Container][]*Container) {
+ dependencies = make(map[*Container][]*Container)
+ for _, node := range cg.nodes {
+ dependsOn := make([]*Container, len(node.dependsOn))
+ for i, d := range node.dependsOn {
+ dependsOn[i] = d.container
+ }
+ dependencies[node.container] = dependsOn
+ }
+ return dependencies
+}
+
+// BuildContainerGraph builds a dependency graph based on the container slice.
+func BuildContainerGraph(ctrs []*Container) (*ContainerGraph, error) {
+ graph := new(ContainerGraph)
graph.nodes = make(map[string]*containerNode)
graph.notDependedOnNodes = make(map[string]*containerNode)
@@ -78,7 +94,7 @@ func buildContainerGraph(ctrs []*Container) (*containerGraph, error) {
// Detect cycles in a container graph using Tarjan's strongly connected
// components algorithm
// Return true if a cycle is found, false otherwise
-func detectCycles(graph *containerGraph) (bool, error) {
+func detectCycles(graph *ContainerGraph) (bool, error) {
type nodeInfo struct {
index int
lowLink int
diff --git a/libpod/container_graph_test.go b/libpod/container_graph_test.go
index d1a52658d..38f03c59c 100644
--- a/libpod/container_graph_test.go
+++ b/libpod/container_graph_test.go
@@ -8,7 +8,7 @@ import (
)
func TestBuildContainerGraphNoCtrsIsEmpty(t *testing.T) {
- graph, err := buildContainerGraph([]*Container{})
+ graph, err := BuildContainerGraph([]*Container{})
assert.NoError(t, err)
assert.Equal(t, 0, len(graph.nodes))
assert.Equal(t, 0, len(graph.noDepNodes))
@@ -24,7 +24,7 @@ func TestBuildContainerGraphOneCtr(t *testing.T) {
ctr1, err := getTestCtr1(manager)
assert.NoError(t, err)
- graph, err := buildContainerGraph([]*Container{ctr1})
+ graph, err := BuildContainerGraph([]*Container{ctr1})
assert.NoError(t, err)
assert.Equal(t, 1, len(graph.nodes))
assert.Equal(t, 1, len(graph.noDepNodes))
@@ -49,7 +49,7 @@ func TestBuildContainerGraphTwoCtrNoEdge(t *testing.T) {
ctr2, err := getTestCtr2(manager)
assert.NoError(t, err)
- graph, err := buildContainerGraph([]*Container{ctr1, ctr2})
+ graph, err := BuildContainerGraph([]*Container{ctr1, ctr2})
assert.NoError(t, err)
assert.Equal(t, 2, len(graph.nodes))
assert.Equal(t, 2, len(graph.noDepNodes))
@@ -76,7 +76,7 @@ func TestBuildContainerGraphTwoCtrOneEdge(t *testing.T) {
assert.NoError(t, err)
ctr2.config.UserNsCtr = ctr1.config.ID
- graph, err := buildContainerGraph([]*Container{ctr1, ctr2})
+ graph, err := BuildContainerGraph([]*Container{ctr1, ctr2})
assert.NoError(t, err)
assert.Equal(t, 2, len(graph.nodes))
assert.Equal(t, 1, len(graph.noDepNodes))
@@ -99,7 +99,7 @@ func TestBuildContainerGraphTwoCtrCycle(t *testing.T) {
ctr2.config.UserNsCtr = ctr1.config.ID
ctr1.config.NetNsCtr = ctr2.config.ID
- _, err = buildContainerGraph([]*Container{ctr1, ctr2})
+ _, err = BuildContainerGraph([]*Container{ctr1, ctr2})
assert.Error(t, err)
}
@@ -116,7 +116,7 @@ func TestBuildContainerGraphThreeCtrNoEdges(t *testing.T) {
ctr3, err := getTestCtrN("3", manager)
assert.NoError(t, err)
- graph, err := buildContainerGraph([]*Container{ctr1, ctr2, ctr3})
+ graph, err := BuildContainerGraph([]*Container{ctr1, ctr2, ctr3})
assert.NoError(t, err)
assert.Equal(t, 3, len(graph.nodes))
assert.Equal(t, 3, len(graph.noDepNodes))
@@ -150,7 +150,7 @@ func TestBuildContainerGraphThreeContainersTwoInCycle(t *testing.T) {
ctr1.config.UserNsCtr = ctr2.config.ID
ctr2.config.IPCNsCtr = ctr1.config.ID
- _, err = buildContainerGraph([]*Container{ctr1, ctr2, ctr3})
+ _, err = BuildContainerGraph([]*Container{ctr1, ctr2, ctr3})
assert.Error(t, err)
}
@@ -170,7 +170,7 @@ func TestBuildContainerGraphThreeContainersCycle(t *testing.T) {
ctr2.config.IPCNsCtr = ctr3.config.ID
ctr3.config.NetNsCtr = ctr1.config.ID
- _, err = buildContainerGraph([]*Container{ctr1, ctr2, ctr3})
+ _, err = BuildContainerGraph([]*Container{ctr1, ctr2, ctr3})
assert.Error(t, err)
}
@@ -190,7 +190,7 @@ func TestBuildContainerGraphThreeContainersNoCycle(t *testing.T) {
ctr1.config.NetNsCtr = ctr3.config.ID
ctr2.config.IPCNsCtr = ctr3.config.ID
- graph, err := buildContainerGraph([]*Container{ctr1, ctr2, ctr3})
+ graph, err := BuildContainerGraph([]*Container{ctr1, ctr2, ctr3})
assert.NoError(t, err)
assert.Equal(t, 3, len(graph.nodes))
assert.Equal(t, 1, len(graph.noDepNodes))
@@ -215,7 +215,7 @@ func TestBuildContainerGraphFourContainersNoEdges(t *testing.T) {
ctr4, err := getTestCtrN("4", manager)
assert.NoError(t, err)
- graph, err := buildContainerGraph([]*Container{ctr1, ctr2, ctr3, ctr4})
+ graph, err := BuildContainerGraph([]*Container{ctr1, ctr2, ctr3, ctr4})
assert.NoError(t, err)
assert.Equal(t, 4, len(graph.nodes))
assert.Equal(t, 4, len(graph.noDepNodes))
@@ -256,7 +256,7 @@ func TestBuildContainerGraphFourContainersTwoInCycle(t *testing.T) {
ctr1.config.IPCNsCtr = ctr2.config.ID
ctr2.config.UserNsCtr = ctr1.config.ID
- _, err = buildContainerGraph([]*Container{ctr1, ctr2, ctr3, ctr4})
+ _, err = BuildContainerGraph([]*Container{ctr1, ctr2, ctr3, ctr4})
assert.Error(t, err)
}
@@ -280,7 +280,7 @@ func TestBuildContainerGraphFourContainersAllInCycle(t *testing.T) {
ctr3.config.NetNsCtr = ctr4.config.ID
ctr4.config.UTSNsCtr = ctr1.config.ID
- _, err = buildContainerGraph([]*Container{ctr1, ctr2, ctr3, ctr4})
+ _, err = BuildContainerGraph([]*Container{ctr1, ctr2, ctr3, ctr4})
assert.Error(t, err)
}
@@ -303,7 +303,7 @@ func TestBuildContainerGraphFourContainersNoneInCycle(t *testing.T) {
ctr1.config.NetNsCtr = ctr3.config.ID
ctr2.config.UserNsCtr = ctr3.config.ID
- graph, err := buildContainerGraph([]*Container{ctr1, ctr2, ctr3, ctr4})
+ graph, err := BuildContainerGraph([]*Container{ctr1, ctr2, ctr3, ctr4})
assert.NoError(t, err)
assert.Equal(t, 4, len(graph.nodes))
assert.Equal(t, 2, len(graph.noDepNodes))
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 313f67963..f51b53e85 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -788,7 +788,7 @@ func (c *Container) startDependencies(ctx context.Context) error {
}
// Build a dependency graph of containers
- graph, err := buildContainerGraph(depCtrs)
+ graph, err := BuildContainerGraph(depCtrs)
if err != nil {
return errors.Wrapf(err, "error generating dependency graph for container %s", c.ID())
}
diff --git a/libpod/image/image.go b/libpod/image/image.go
index 068491f28..1ff271a4d 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"io"
+ "io/ioutil"
"os"
"path/filepath"
"strings"
@@ -135,7 +136,7 @@ func (ir *Runtime) NewFromLocal(name string) (*Image, error) {
// 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, forcePull bool, label *string) (*Image, error) {
+func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile string, writer io.Writer, dockeroptions *DockerRegistryOptions, signingoptions SigningOptions, label *string, pullType util.PullType) (*Image, error) {
span, _ := opentracing.StartSpanFromContext(ctx, "newImage")
span.SetTag("type", "runtime")
defer span.Finish()
@@ -145,11 +146,13 @@ func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile
InputName: name,
imageruntime: ir,
}
- if !forcePull {
+ if pullType != util.PullImageAlways {
localImage, err := newImage.getLocalImage()
if err == nil {
newImage.image = localImage
return &newImage, nil
+ } else if pullType == util.PullImageNever {
+ return nil, err
}
}
@@ -553,7 +556,7 @@ func (i *Image) UntagImage(tag string) error {
// 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, signaturePolicyPath string, writer io.Writer, forceCompress bool, signingOptions SigningOptions, dockerRegistryOptions *DockerRegistryOptions, additionalDockerArchiveTags []reference.NamedTagged) error {
+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) error {
if destination == "" {
return errors.Wrapf(syscall.EINVAL, "destination image name must be specified")
}
@@ -571,11 +574,11 @@ func (i *Image) PushImageToHeuristicDestination(ctx context.Context, destination
return err
}
}
- return i.PushImageToReference(ctx, dest, manifestMIMEType, authFile, signaturePolicyPath, writer, forceCompress, signingOptions, dockerRegistryOptions, additionalDockerArchiveTags)
+ return i.PushImageToReference(ctx, dest, manifestMIMEType, authFile, digestFile, signaturePolicyPath, writer, forceCompress, signingOptions, dockerRegistryOptions, additionalDockerArchiveTags)
}
// 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, signaturePolicyPath string, writer io.Writer, forceCompress bool, signingOptions SigningOptions, dockerRegistryOptions *DockerRegistryOptions, additionalDockerArchiveTags []reference.NamedTagged) error {
+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) error {
sc := GetSystemContext(signaturePolicyPath, authFile, forceCompress)
sc.BlobInfoCacheDir = filepath.Join(i.imageruntime.store.GraphRoot(), "cache")
@@ -597,10 +600,22 @@ func (i *Image) PushImageToReference(ctx context.Context, dest types.ImageRefere
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.
// Copy the image to the remote destination
- _, err = cp.Image(ctx, policyContext, dest, src, copyOptions)
+ 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
}
@@ -1356,7 +1371,7 @@ func (i *Image) Save(ctx context.Context, source, format, output string, moreTag
return err
}
}
- if err := i.PushImageToReference(ctx, destRef, manifestType, "", "", writer, compress, SigningOptions{}, &DockerRegistryOptions{}, additionaltags); err != nil {
+ if err := i.PushImageToReference(ctx, destRef, manifestType, "", "", "", writer, compress, SigningOptions{}, &DockerRegistryOptions{}, additionaltags); err != nil {
return errors.Wrapf(err, "unable to save %q", source)
}
i.newImageEvent(events.Save)
diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go
index e93ebf797..5a6d095f6 100644
--- a/libpod/image/image_test.go
+++ b/libpod/image/image_test.go
@@ -3,12 +3,13 @@ package image
import (
"context"
"fmt"
- "github.com/containers/libpod/libpod/events"
"io"
"io/ioutil"
"os"
"testing"
+ "github.com/containers/libpod/libpod/events"
+ "github.com/containers/libpod/pkg/util"
"github.com/containers/storage"
"github.com/opencontainers/go-digest"
"github.com/stretchr/testify/assert"
@@ -89,9 +90,9 @@ func TestImage_NewFromLocal(t *testing.T) {
ir, err := NewImageRuntimeFromOptions(so)
assert.NoError(t, err)
ir.Eventer = events.NewNullEventer()
- bb, err := ir.New(context.Background(), "docker.io/library/busybox:latest", "", "", writer, nil, SigningOptions{}, false, nil)
+ bb, err := ir.New(context.Background(), "docker.io/library/busybox:latest", "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing)
assert.NoError(t, err)
- bbglibc, err := ir.New(context.Background(), "docker.io/library/busybox:glibc", "", "", writer, nil, SigningOptions{}, false, nil)
+ bbglibc, err := ir.New(context.Background(), "docker.io/library/busybox:glibc", "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing)
assert.NoError(t, err)
tm, err := makeLocalMatrix(bb, bbglibc)
@@ -139,7 +140,7 @@ func TestImage_New(t *testing.T) {
// Iterate over the names and delete the image
// after the pull
for _, img := range names {
- newImage, err := ir.New(context.Background(), img, "", "", writer, nil, SigningOptions{}, false, nil)
+ newImage, err := ir.New(context.Background(), img, "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing)
assert.NoError(t, err)
assert.NotEqual(t, newImage.ID(), "")
err = newImage.Remove(context.Background(), false)
@@ -168,7 +169,7 @@ func TestImage_MatchRepoTag(t *testing.T) {
ir, err := NewImageRuntimeFromOptions(so)
assert.NoError(t, err)
ir.Eventer = events.NewNullEventer()
- newImage, err := ir.New(context.Background(), "busybox", "", "", os.Stdout, nil, SigningOptions{}, false, nil)
+ newImage, err := ir.New(context.Background(), "busybox", "", "", os.Stdout, nil, SigningOptions{}, nil, util.PullImageMissing)
assert.NoError(t, err)
err = newImage.TagImage("foo:latest")
assert.NoError(t, err)
diff --git a/libpod/image/pull.go b/libpod/image/pull.go
index 78cfe3626..dbf3a4ef5 100644
--- a/libpod/image/pull.go
+++ b/libpod/image/pull.go
@@ -13,6 +13,7 @@ import (
dockerarchive "github.com/containers/image/docker/archive"
"github.com/containers/image/docker/tarfile"
ociarchive "github.com/containers/image/oci/archive"
+ oci "github.com/containers/image/oci/layout"
is "github.com/containers/image/storage"
"github.com/containers/image/transports"
"github.com/containers/image/transports/alltransports"
@@ -37,6 +38,9 @@ var (
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
@@ -189,12 +193,12 @@ func (ir *Runtime) pullGoalFromImageReference(ctx context.Context, srcRef types.
return ir.getSinglePullRefPairGoal(srcRef, dest)
case DirTransport:
- path := srcRef.StringWithinTransport()
- image := path
- if image[:1] == "/" {
- // Set localhost as the registry so docker.io isn't prepended, and the path becomes the repository
- image = DefaultLocalRegistry + image
- }
+ 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:
@@ -202,6 +206,15 @@ func (ir *Runtime) pullGoalFromImageReference(ctx context.Context, srcRef types.
}
}
+// 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, label *string) ([]string, error) {
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index bef3f7739..fd14b2f73 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -17,7 +17,6 @@ import (
cnitypes "github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containers/libpod/pkg/errorhandling"
- "github.com/containers/libpod/pkg/firewall"
"github.com/containers/libpod/pkg/netns"
"github.com/containers/libpod/pkg/rootless"
"github.com/cri-o/ocicni/pkg/ocicni"
@@ -86,18 +85,6 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Re
networkStatus = append(networkStatus, resultCurrent)
}
- // Add firewall rules to ensure the container has network access.
- // Will not be necessary once CNI firewall plugin merges upstream.
- // https://github.com/containernetworking/plugins/pull/75
- for _, netStatus := range networkStatus {
- firewallConf := &firewall.FirewallNetConf{
- PrevResult: netStatus,
- }
- if err := r.firewallBackend.Add(firewallConf); err != nil {
- return nil, errors.Wrapf(err, "error adding firewall rules for container %s", ctr.ID())
- }
- }
-
return networkStatus, nil
}
@@ -390,26 +377,12 @@ func (r *Runtime) closeNetNS(ctr *Container) error {
}
// Tear down a network namespace, undoing all state associated with it.
-// The CNI firewall rules will be removed, the namespace will be unmounted,
-// and the file descriptor associated with it closed.
func (r *Runtime) teardownNetNS(ctr *Container) error {
if ctr.state.NetNS == nil {
// The container has no network namespace, we're set
return nil
}
- // Remove firewall rules we added on configuring the container.
- // Will not be necessary once CNI firewall plugin merges upstream.
- // https://github.com/containernetworking/plugins/pull/75
- for _, netStatus := range ctr.state.NetworkStatus {
- firewallConf := &firewall.FirewallNetConf{
- PrevResult: netStatus,
- }
- if err := r.firewallBackend.Del(firewallConf); err != nil {
- return errors.Wrapf(err, "error removing firewall rules for container %s", ctr.ID())
- }
- }
-
logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID())
var requestedIP net.IP
diff --git a/libpod/oci.go b/libpod/oci.go
index 2eb004b84..8a873ca5b 100644
--- a/libpod/oci.go
+++ b/libpod/oci.go
@@ -60,6 +60,7 @@ type OCIRuntime struct {
noPivot bool
reservePorts bool
supportsJSON bool
+ sdNotify bool
}
// ociError is used to parse the OCI runtime JSON log. It is not part of the
@@ -87,6 +88,7 @@ func newOCIRuntime(name string, paths []string, conmonPath string, runtimeCfg *R
runtime.logSizeMax = runtimeCfg.MaxLogSize
runtime.noPivot = runtimeCfg.NoPivotRoot
runtime.reservePorts = runtimeCfg.EnablePortReservation
+ runtime.sdNotify = runtimeCfg.SDNotify
// TODO: probe OCI runtime for feature and enable automatically if
// available.
@@ -211,7 +213,7 @@ func bindPorts(ports []ocicni.PortMapping) ([]*os.File, error) {
func (r *OCIRuntime) updateContainerStatus(ctr *Container, useRuntime bool) error {
exitFile := ctr.exitFilePath()
- runtimeDir, err := util.GetRootlessRuntimeDir()
+ runtimeDir, err := util.GetRuntimeDir()
if err != nil {
return err
}
@@ -334,7 +336,7 @@ func (r *OCIRuntime) updateContainerStatus(ctr *Container, useRuntime bool) erro
// Sets time the container was started, but does not save it.
func (r *OCIRuntime) startContainer(ctr *Container) error {
// TODO: streams should probably *not* be our STDIN/OUT/ERR - redirect to buffers?
- runtimeDir, err := util.GetRootlessRuntimeDir()
+ runtimeDir, err := util.GetRuntimeDir()
if err != nil {
return err
}
@@ -354,7 +356,7 @@ func (r *OCIRuntime) startContainer(ctr *Container) error {
// killContainer sends the given signal to the given container
func (r *OCIRuntime) killContainer(ctr *Container, signal uint) error {
logrus.Debugf("Sending signal %d to container %s", signal, ctr.ID())
- runtimeDir, err := util.GetRootlessRuntimeDir()
+ runtimeDir, err := util.GetRuntimeDir()
if err != nil {
return err
}
@@ -368,7 +370,7 @@ func (r *OCIRuntime) killContainer(ctr *Container, signal uint) error {
// deleteContainer deletes a container from the OCI runtime
func (r *OCIRuntime) deleteContainer(ctr *Container) error {
- runtimeDir, err := util.GetRootlessRuntimeDir()
+ runtimeDir, err := util.GetRuntimeDir()
if err != nil {
return err
}
@@ -378,7 +380,7 @@ func (r *OCIRuntime) deleteContainer(ctr *Container) error {
// pauseContainer pauses the given container
func (r *OCIRuntime) pauseContainer(ctr *Container) error {
- runtimeDir, err := util.GetRootlessRuntimeDir()
+ runtimeDir, err := util.GetRuntimeDir()
if err != nil {
return err
}
@@ -388,7 +390,7 @@ func (r *OCIRuntime) pauseContainer(ctr *Container) error {
// unpauseContainer unpauses the given container
func (r *OCIRuntime) unpauseContainer(ctr *Container) error {
- runtimeDir, err := util.GetRootlessRuntimeDir()
+ runtimeDir, err := util.GetRuntimeDir()
if err != nil {
return err
}
diff --git a/libpod/oci_internal_linux.go b/libpod/oci_internal_linux.go
index e2c73f5ed..48b7370e0 100644
--- a/libpod/oci_internal_linux.go
+++ b/libpod/oci_internal_linux.go
@@ -36,7 +36,7 @@ import (
func (r *OCIRuntime) createOCIContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (err error) {
var stderrBuf bytes.Buffer
- runtimeDir, err := util.GetRootlessRuntimeDir()
+ runtimeDir, err := util.GetRuntimeDir()
if err != nil {
return err
}
@@ -247,10 +247,14 @@ func (r *OCIRuntime) configureConmonEnv(runtimeDir string) ([]string, []*os.File
if notify, ok := os.LookupEnv("NOTIFY_SOCKET"); ok {
env = append(env, fmt.Sprintf("NOTIFY_SOCKET=%s", notify))
}
- if listenfds, ok := os.LookupEnv("LISTEN_FDS"); ok {
- env = append(env, fmt.Sprintf("LISTEN_FDS=%s", listenfds), "LISTEN_PID=1")
- fds := activation.Files(false)
- extraFiles = append(extraFiles, fds...)
+ if !r.sdNotify {
+ if listenfds, ok := os.LookupEnv("LISTEN_FDS"); ok {
+ env = append(env, fmt.Sprintf("LISTEN_FDS=%s", listenfds), "LISTEN_PID=1")
+ fds := activation.Files(false)
+ extraFiles = append(extraFiles, fds...)
+ }
+ } else {
+ logrus.Debug("disabling SD notify")
}
return env, extraFiles, nil
}
@@ -445,6 +449,15 @@ func readConmonPipeData(pipe *os.File, ociLog string) (int, error) {
select {
case ss := <-ch:
if ss.err != nil {
+ if ociLog != "" {
+ ociLogData, err := ioutil.ReadFile(ociLog)
+ if err == nil {
+ var ociErr ociError
+ if err := json.Unmarshal(ociLogData, &ociErr); err == nil {
+ return -1, getOCIRuntimeError(ociErr.Msg)
+ }
+ }
+ }
return -1, errors.Wrapf(ss.err, "error reading container (probably exited) json message")
}
logrus.Debugf("Received: %d", ss.si.Data)
@@ -472,10 +485,11 @@ func readConmonPipeData(pipe *os.File, ociLog string) (int, error) {
}
func getOCIRuntimeError(runtimeMsg string) error {
- if match, _ := regexp.MatchString(".*permission denied.*", runtimeMsg); match {
+ r := strings.ToLower(runtimeMsg)
+ if match, _ := regexp.MatchString(".*permission denied.*|.*operation not permitted.*", r); match {
return errors.Wrapf(define.ErrOCIRuntimePermissionDenied, "%s", strings.Trim(runtimeMsg, "\n"))
}
- if match, _ := regexp.MatchString(".*executable file not found in.*", runtimeMsg); match {
+ if match, _ := regexp.MatchString(".*executable file not found in.*|.*no such file or directory.*", r); match {
return errors.Wrapf(define.ErrOCIRuntimeNotFound, "%s", strings.Trim(runtimeMsg, "\n"))
}
return errors.Wrapf(define.ErrOCIRuntime, "%s", strings.Trim(runtimeMsg, "\n"))
diff --git a/libpod/oci_linux.go b/libpod/oci_linux.go
index 45365203e..1613c3e68 100644
--- a/libpod/oci_linux.go
+++ b/libpod/oci_linux.go
@@ -208,7 +208,7 @@ func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty
}
}()
- runtimeDir, err := util.GetRootlessRuntimeDir()
+ runtimeDir, err := util.GetRuntimeDir()
if err != nil {
return -1, nil, err
}
@@ -437,7 +437,7 @@ func (r *OCIRuntime) stopContainer(ctr *Container, timeout uint) error {
args = []string{"kill", "--all", ctr.ID(), "KILL"}
}
- runtimeDir, err := util.GetRootlessRuntimeDir()
+ runtimeDir, err := util.GetRuntimeDir()
if err != nil {
return err
}
@@ -487,7 +487,7 @@ func (r *OCIRuntime) execStopContainer(ctr *Container, timeout uint) error {
if len(execSessions) == 0 {
return nil
}
- runtimeDir, err := util.GetRootlessRuntimeDir()
+ runtimeDir, err := util.GetRuntimeDir()
if err != nil {
return err
}
diff --git a/libpod/options.go b/libpod/options.go
index 7fbd0016a..a7ddbec34 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -482,6 +482,15 @@ func WithEventsLogger(logger string) RuntimeOption {
}
}
+// WithEnableSDNotify sets a runtime option so we know whether to disable socket/FD
+// listening
+func WithEnableSDNotify() RuntimeOption {
+ return func(rt *Runtime) error {
+ rt.config.SDNotify = true
+ return nil
+ }
+}
+
// Container Creation Options
// WithShmDir sets the directory that should be mounted on /dev/shm.
@@ -1362,6 +1371,17 @@ func WithNamedVolumes(volumes []*ContainerNamedVolume) CtrCreateOption {
}
}
+// WithHealthCheck adds the healthcheck to the container config
+func WithHealthCheck(healthCheck *manifest.Schema2HealthConfig) CtrCreateOption {
+ return func(ctr *Container) error {
+ if ctr.valid {
+ return define.ErrCtrFinalized
+ }
+ ctr.config.HealthCheckConfig = healthCheck
+ return nil
+ }
+}
+
// Volume Creation Options
// WithVolumeName sets the name of the volume.
@@ -1381,30 +1401,30 @@ func WithVolumeName(name string) VolumeCreateOption {
}
}
-// WithVolumeLabels sets the labels of the volume.
-func WithVolumeLabels(labels map[string]string) VolumeCreateOption {
+// WithVolumeDriver sets the volume's driver.
+// It is presently not implemented, but will be supported in a future Podman
+// release.
+func WithVolumeDriver(driver string) VolumeCreateOption {
return func(volume *Volume) error {
if volume.valid {
return define.ErrVolumeFinalized
}
- volume.config.Labels = make(map[string]string)
- for key, value := range labels {
- volume.config.Labels[key] = value
- }
-
- return nil
+ return define.ErrNotImplemented
}
}
-// WithVolumeDriver sets the driver of the volume.
-func WithVolumeDriver(driver string) VolumeCreateOption {
+// WithVolumeLabels sets the labels of the volume.
+func WithVolumeLabels(labels map[string]string) VolumeCreateOption {
return func(volume *Volume) error {
if volume.valid {
return define.ErrVolumeFinalized
}
- volume.config.Driver = driver
+ volume.config.Labels = make(map[string]string)
+ for key, value := range labels {
+ volume.config.Labels[key] = value
+ }
return nil
}
@@ -1488,6 +1508,24 @@ func WithPodName(name string) PodCreateOption {
}
}
+// WithPodHostname sets the hostname of the pod.
+func WithPodHostname(hostname string) PodCreateOption {
+ return func(pod *Pod) error {
+ if pod.valid {
+ return define.ErrPodFinalized
+ }
+
+ // Check the hostname against a regex
+ if !nameRegex.MatchString(hostname) {
+ return regexError
+ }
+
+ pod.config.Hostname = hostname
+
+ return nil
+ }
+}
+
// WithPodLabels sets the labels of a pod.
func WithPodLabels(labels map[string]string) PodCreateOption {
return func(pod *Pod) error {
@@ -1673,14 +1711,3 @@ func WithInfraContainerPorts(bindings []ocicni.PortMapping) PodCreateOption {
return nil
}
}
-
-// WithHealthCheck adds the healthcheck to the container config
-func WithHealthCheck(healthCheck *manifest.Schema2HealthConfig) CtrCreateOption {
- return func(ctr *Container) error {
- if ctr.valid {
- return define.ErrCtrFinalized
- }
- ctr.config.HealthCheckConfig = healthCheck
- return nil
- }
-}
diff --git a/libpod/pod.go b/libpod/pod.go
index 60626bfd7..3b9bb9c60 100644
--- a/libpod/pod.go
+++ b/libpod/pod.go
@@ -36,6 +36,8 @@ type PodConfig struct {
// Namespace the pod is in
Namespace string `json:"namespace,omitempty"`
+ Hostname string `json:"hostname,omitempty"`
+
// Labels contains labels applied to the pod
Labels map[string]string `json:"labels"`
// CgroupParent contains the pod's CGroup parent
diff --git a/libpod/pod_api.go b/libpod/pod_api.go
index c7b0353bd..e2448e92a 100644
--- a/libpod/pod_api.go
+++ b/libpod/pod_api.go
@@ -37,7 +37,7 @@ func (p *Pod) Start(ctx context.Context) (map[string]error, error) {
}
// Build a dependency graph of containers in the pod
- graph, err := buildContainerGraph(allCtrs)
+ graph, err := BuildContainerGraph(allCtrs)
if err != nil {
return nil, errors.Wrapf(err, "error generating dependency graph for pod %s", p.ID())
}
@@ -289,7 +289,7 @@ func (p *Pod) Restart(ctx context.Context) (map[string]error, error) {
}
// Build a dependency graph of containers in the pod
- graph, err := buildContainerGraph(allCtrs)
+ graph, err := BuildContainerGraph(allCtrs)
if err != nil {
return nil, errors.Wrapf(err, "error generating dependency graph for pod %s", p.ID())
}
diff --git a/libpod/runtime.go b/libpod/runtime.go
index 8a4eee081..4d6a80d0b 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -23,7 +23,6 @@ import (
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/libpod/lock"
- "github.com/containers/libpod/pkg/firewall"
sysreg "github.com/containers/libpod/pkg/registries"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/util"
@@ -108,7 +107,6 @@ type Runtime struct {
netPlugin ocicni.CNIPlugin
conmonPath string
imageRuntime *image.Runtime
- firewallBackend firewall.FirewallBackend
lockManager lock.Manager
configuredFrom *runtimeConfiguredFrom
@@ -252,6 +250,10 @@ type RuntimeConfig struct {
EventsLogFilePath string `toml:"-events_logfile_path"`
//DetachKeys is the sequence of keys used to detach a container
DetachKeys string `toml:"detach_keys"`
+
+ // SDNotify tells Libpod to allow containers to notify the host
+ // systemd of readiness using the SD_NOTIFY mechanism
+ SDNotify bool
}
// runtimeConfiguredFrom is a struct used during early runtime init to help
@@ -365,7 +367,7 @@ func SetXdgDirs() error {
if runtimeDir == "" {
var err error
- runtimeDir, err = util.GetRootlessRuntimeDir()
+ runtimeDir, err = util.GetRuntimeDir()
if err != nil {
return err
}
@@ -391,11 +393,11 @@ func getDefaultTmpDir() (string, error) {
return "/var/run/libpod", nil
}
- rootlessRuntimeDir, err := util.GetRootlessRuntimeDir()
+ runtimeDir, err := util.GetRuntimeDir()
if err != nil {
return "", err
}
- libpodRuntimeDir := filepath.Join(rootlessRuntimeDir, "libpod")
+ libpodRuntimeDir := filepath.Join(runtimeDir, "libpod")
if err := os.Mkdir(libpodRuntimeDir, 0700|os.ModeSticky); err != nil {
if !os.IsExist(err) {
@@ -1106,17 +1108,6 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) {
runtime.netPlugin = netPlugin
}
- // Set up a firewall backend
- backendType := ""
- if rootless.IsRootless() {
- backendType = "none"
- }
- fwBackend, err := firewall.GetBackend(backendType)
- if err != nil {
- return err
- }
- runtime.firewallBackend = fwBackend
-
// We now need to see if the system has restarted
// We check for the presence of a file in our tmp directory to verify this
// This check must be locked to prevent races
diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go
index da35b7f93..ad6662f03 100644
--- a/libpod/runtime_pod_infra_linux.go
+++ b/libpod/runtime_pod_infra_linux.go
@@ -9,6 +9,7 @@ import (
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/rootless"
+ "github.com/containers/libpod/pkg/util"
"github.com/opencontainers/image-spec/specs-go/v1"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
@@ -30,6 +31,9 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID
return nil, err
}
+ // Set Pod hostname
+ g.Config.Hostname = p.config.Hostname
+
isRootless := rootless.IsRootless()
entryCmd := []string{r.config.InfraCommand}
@@ -108,7 +112,7 @@ func (r *Runtime) createInfraContainer(ctx context.Context, p *Pod) (*Container,
return nil, define.ErrRuntimeStopped
}
- newImage, err := r.ImageRuntime().New(ctx, r.config.InfraImage, "", "", nil, nil, image.SigningOptions{}, false, nil)
+ newImage, err := r.ImageRuntime().New(ctx, r.config.InfraImage, "", "", nil, nil, image.SigningOptions{}, nil, util.PullImageMissing)
if err != nil {
return nil, err
}
diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go
index f38e6e7c1..05866d05a 100644
--- a/libpod/runtime_pod_linux.go
+++ b/libpod/runtime_pod_linux.go
@@ -52,6 +52,10 @@ func (r *Runtime) NewPod(ctx context.Context, options ...PodCreateOption) (_ *Po
pod.config.Name = name
}
+ if pod.config.Hostname == "" {
+ pod.config.Hostname = pod.config.Name
+ }
+
// Allocate a lock for the pod
lock, err := r.lockManager.AllocateLock()
if err != nil {
@@ -200,7 +204,7 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool)
// Get the conmon CGroup
conmonCgroupPath := filepath.Join(p.state.CgroupPath, "conmon")
conmonCgroup, err := cgroups.Load(conmonCgroupPath)
- if err != nil && err != cgroups.ErrCgroupDeleted {
+ if err != nil && err != cgroups.ErrCgroupDeleted && err != cgroups.ErrCgroupV1Rootless {
removalErr = errors.Wrapf(err, "error retrieving pod %s conmon cgroup %s", p.ID(), conmonCgroupPath)
}
@@ -262,7 +266,7 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool)
// hard - instead, just log errors.
conmonCgroupPath := filepath.Join(p.state.CgroupPath, "conmon")
conmonCgroup, err := cgroups.Load(conmonCgroupPath)
- if err != nil && err != cgroups.ErrCgroupDeleted {
+ if err != nil && err != cgroups.ErrCgroupDeleted && err != cgroups.ErrCgroupV1Rootless {
if removalErr == nil {
removalErr = errors.Wrapf(err, "error retrieving pod %s conmon cgroup", p.ID())
} else {
@@ -279,7 +283,7 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool)
}
}
cgroup, err := cgroups.Load(p.state.CgroupPath)
- if err != nil && err != cgroups.ErrCgroupDeleted {
+ if err != nil && err != cgroups.ErrCgroupDeleted && err != cgroups.ErrCgroupV1Rootless {
if removalErr == nil {
removalErr = errors.Wrapf(err, "error retrieving pod %s cgroup", p.ID())
} else {
diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go
index ac6fd02c3..84703787d 100644
--- a/libpod/runtime_volume_linux.go
+++ b/libpod/runtime_volume_linux.go
@@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"strings"
+ "time"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/events"
@@ -42,14 +43,10 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption)
if volume.config.Name == "" {
volume.config.Name = stringid.GenerateNonCryptoID()
}
- // TODO: support for other volume drivers
if volume.config.Driver == "" {
volume.config.Driver = "local"
}
- // TODO: determine when the scope is global and set it to that
- if volume.config.Scope == "" {
- volume.config.Scope = "local"
- }
+ volume.config.CreatedTime = time.Now()
// Create the mountpoint of this volume
volPathRoot := filepath.Join(r.config.VolumePath, volume.config.Name)
diff --git a/libpod/volume.go b/libpod/volume.go
index 9ed2ff087..74126b49b 100644
--- a/libpod/volume.go
+++ b/libpod/volume.go
@@ -1,5 +1,9 @@
package libpod
+import (
+ "time"
+)
+
// Volume is the type used to create named volumes
// TODO: all volumes should be created using this and the Volume API
type Volume struct {
@@ -15,10 +19,10 @@ type VolumeConfig struct {
Name string `json:"name"`
Labels map[string]string `json:"labels"`
- MountPoint string `json:"mountPoint"`
Driver string `json:"driver"`
+ MountPoint string `json:"mountPoint"`
+ CreatedTime time.Time `json:"createdAt,omitempty"`
Options map[string]string `json:"options"`
- Scope string `json:"scope"`
IsCtrSpecific bool `json:"ctrSpecific"`
UID int `json:"uid"`
GID int `json:"gid"`
@@ -29,6 +33,18 @@ func (v *Volume) Name() string {
return v.config.Name
}
+// Driver retrieves the volume's driver.
+func (v *Volume) Driver() string {
+ return v.config.Driver
+}
+
+// Scope retrieves the volume's scope.
+// Libpod does not implement volume scoping, and this is provided solely for
+// Docker compatibility. It returns only "local".
+func (v *Volume) Scope() string {
+ return "local"
+}
+
// Labels returns the volume's labels
func (v *Volume) Labels() map[string]string {
labels := make(map[string]string)
@@ -43,11 +59,6 @@ func (v *Volume) MountPoint() string {
return v.config.MountPoint
}
-// Driver returns the volume's driver
-func (v *Volume) Driver() string {
- return v.config.Driver
-}
-
// Options return the volume's options
func (v *Volume) Options() map[string]string {
options := make(map[string]string)
@@ -58,14 +69,25 @@ func (v *Volume) Options() map[string]string {
return options
}
-// Scope returns the scope of the volume
-func (v *Volume) Scope() string {
- return v.config.Scope
-}
-
// IsCtrSpecific returns whether this volume was created specifically for a
// given container. Images with this set to true will be removed when the
// container is removed with the Volumes parameter set to true.
func (v *Volume) IsCtrSpecific() bool {
return v.config.IsCtrSpecific
}
+
+// UID returns the UID the volume will be created as.
+func (v *Volume) UID() int {
+ return v.config.UID
+}
+
+// GID returns the GID the volume will be created as.
+func (v *Volume) GID() int {
+ return v.config.GID
+}
+
+// CreatedTime returns the time the volume was created at. It was not tracked
+// for some time, so older volumes may not contain one.
+func (v *Volume) CreatedTime() time.Time {
+ return v.config.CreatedTime
+}
diff --git a/libpod/volume_inspect.go b/libpod/volume_inspect.go
new file mode 100644
index 000000000..87ed9d340
--- /dev/null
+++ b/libpod/volume_inspect.go
@@ -0,0 +1,70 @@
+package libpod
+
+import (
+ "time"
+
+ "github.com/containers/libpod/libpod/define"
+)
+
+// InspectVolumeData is the output of Inspect() on a volume. It is matched to
+// the format of 'docker volume inspect'.
+type InspectVolumeData struct {
+ // Name is the name of the volume.
+ Name string `json:"Name"`
+ // Driver is the driver used to create the volume.
+ // This will be properly implemented in a future version.
+ Driver string `json:"Driver"`
+ // Mountpoint is the path on the host where the volume is mounted.
+ Mountpoint string `json:"Mountpoint"`
+ // CreatedAt is the date and time the volume was created at. This is not
+ // stored for older Libpod volumes; if so, it will be omitted.
+ CreatedAt time.Time `json:"CreatedAt,omitempty"`
+ // Status is presently unused and provided only for Docker compatibility.
+ // In the future it will be used to return information on the volume's
+ // current state.
+ Status map[string]string `json:"Status,omitempty"`
+ // Labels includes the volume's configured labels, key:value pairs that
+ // can be passed during volume creation to provide information for third
+ // party tools.
+ Labels map[string]string `json:"Labels"`
+ // Scope is unused and provided solely for Docker compatibility. It is
+ // unconditionally set to "local".
+ Scope string `json:"Scope"`
+ // Options is a set of options that were used when creating the volume.
+ // It is presently not used.
+ Options map[string]string `json:"Options"`
+ // UID is the UID that the volume was created with.
+ UID int `json:"UID,omitempty"`
+ // GID is the GID that the volume was created with.
+ GID int `json:"GID,omitempty"`
+ // ContainerSpecific indicates that the volume was created as part of a
+ // specific container, and will be removed when that container is
+ // removed.
+ ContainerSpecific bool `json:"ContainerSpecific,omitempty"`
+}
+
+// Inspect provides detailed information about the configuration of the given
+// volume.
+func (v *Volume) Inspect() (*InspectVolumeData, error) {
+ if !v.valid {
+ return nil, define.ErrVolumeRemoved
+ }
+
+ data := new(InspectVolumeData)
+
+ data.Name = v.config.Name
+ data.Driver = v.config.Driver
+ data.Mountpoint = v.config.MountPoint
+ data.CreatedAt = v.config.CreatedTime
+ data.Labels = make(map[string]string)
+ for k, v := range v.config.Labels {
+ data.Labels[k] = v
+ }
+ data.Scope = v.Scope()
+ data.Options = make(map[string]string)
+ data.UID = v.config.UID
+ data.GID = v.config.GID
+ data.ContainerSpecific = v.config.IsCtrSpecific
+
+ return data, nil
+}