summaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
Diffstat (limited to 'libpod')
-rw-r--r--libpod/adapter/runtime.go152
-rw-r--r--libpod/adapter/runtime_remote.go234
-rw-r--r--libpod/adapter/volumes_remote.go33
-rw-r--r--libpod/container.go15
-rw-r--r--libpod/container_attach_linux.go4
-rw-r--r--libpod/container_internal.go154
-rw-r--r--libpod/container_internal_linux.go7
-rw-r--r--libpod/errors.go16
-rw-r--r--libpod/image/utils.go17
-rw-r--r--libpod/kube.go6
-rw-r--r--libpod/options.go3
-rw-r--r--libpod/runtime_ctr.go44
-rw-r--r--libpod/runtime_img.go2
-rw-r--r--libpod/runtime_volume.go51
-rw-r--r--libpod/volume_internal.go4
15 files changed, 582 insertions, 160 deletions
diff --git a/libpod/adapter/runtime.go b/libpod/adapter/runtime.go
index 46771b5b6..3146cf5db 100644
--- a/libpod/adapter/runtime.go
+++ b/libpod/adapter/runtime.go
@@ -4,18 +4,22 @@ package adapter
import (
"context"
- "github.com/pkg/errors"
"io"
"io/ioutil"
"os"
"strconv"
+ "github.com/containers/buildah"
+ "github.com/containers/buildah/imagebuildah"
+ "github.com/containers/buildah/pkg/parse"
+ "github.com/containers/image/docker/reference"
"github.com/containers/image/types"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/rootless"
+ "github.com/pkg/errors"
)
// LocalRuntime describes a typical libpod runtime
@@ -34,6 +38,14 @@ type Container struct {
*libpod.Container
}
+// Volume ...
+type Volume struct {
+ *libpod.Volume
+}
+
+// VolumeFilter is for filtering volumes on the client
+type VolumeFilter func(*Volume) bool
+
// GetRuntime returns a LocalRuntime struct with the actual runtime embedded in it
func GetRuntime(c *cliconfig.PodmanCommand) (*LocalRuntime, error) {
runtime, err := libpodruntime.GetRuntime(c)
@@ -155,3 +167,141 @@ func (r *LocalRuntime) Export(name string, path string) error {
func (r *LocalRuntime) Import(ctx context.Context, source, reference string, changes []string, history string, quiet bool) (string, error) {
return r.Runtime.Import(ctx, source, reference, changes, history, quiet)
}
+
+// CreateVolume is a wrapper to create volumes
+func (r *LocalRuntime) CreateVolume(ctx context.Context, c *cliconfig.VolumeCreateValues, labels, opts map[string]string) (string, error) {
+ var (
+ options []libpod.VolumeCreateOption
+ volName string
+ )
+
+ if len(c.InputArgs) > 0 {
+ volName = c.InputArgs[0]
+ options = append(options, libpod.WithVolumeName(volName))
+ }
+
+ if c.Flag("driver").Changed {
+ options = append(options, libpod.WithVolumeDriver(c.Driver))
+ }
+
+ if len(labels) != 0 {
+ options = append(options, libpod.WithVolumeLabels(labels))
+ }
+
+ if len(options) != 0 {
+ options = append(options, libpod.WithVolumeOptions(opts))
+ }
+ newVolume, err := r.NewVolume(ctx, options...)
+ if err != nil {
+ return "", err
+ }
+ return newVolume.Name(), nil
+}
+
+// RemoveVolumes is a wrapper to remove volumes
+func (r *LocalRuntime) RemoveVolumes(ctx context.Context, c *cliconfig.VolumeRmValues) ([]string, error) {
+ return r.Runtime.RemoveVolumes(ctx, c.InputArgs, c.All, c.Force)
+}
+
+// Push is a wrapper to push an image to a registry
+func (r *LocalRuntime) Push(ctx context.Context, srcName, destination, manifestMIMEType, authfile, signaturePolicyPath string, writer io.Writer, forceCompress bool, signingOptions image.SigningOptions, dockerRegistryOptions *image.DockerRegistryOptions, additionalDockerArchiveTags []reference.NamedTagged) error {
+ newImage, err := r.ImageRuntime().NewFromLocal(srcName)
+ if err != nil {
+ return err
+ }
+ return newImage.PushImageToHeuristicDestination(ctx, destination, manifestMIMEType, authfile, signaturePolicyPath, writer, forceCompress, signingOptions, dockerRegistryOptions, nil)
+}
+
+// InspectVolumes returns a slice of volumes based on an arg list or --all
+func (r *LocalRuntime) InspectVolumes(ctx context.Context, c *cliconfig.VolumeInspectValues) ([]*Volume, error) {
+ var (
+ volumes []*libpod.Volume
+ err error
+ )
+
+ if c.All {
+ volumes, err = r.GetAllVolumes()
+ } else {
+ for _, v := range c.InputArgs {
+ vol, err := r.GetVolume(v)
+ if err != nil {
+ return nil, err
+ }
+ volumes = append(volumes, vol)
+ }
+ }
+ if err != nil {
+ return nil, err
+ }
+ return libpodVolumeToVolume(volumes), nil
+}
+
+// Volumes returns a slice of localruntime volumes
+func (r *LocalRuntime) Volumes(ctx context.Context) ([]*Volume, error) {
+ vols, err := r.GetAllVolumes()
+ if err != nil {
+ return nil, err
+ }
+ return libpodVolumeToVolume(vols), nil
+}
+
+// libpodVolumeToVolume converts a slice of libpod volumes to a slice
+// of localruntime volumes (same as libpod)
+func libpodVolumeToVolume(volumes []*libpod.Volume) []*Volume {
+ var vols []*Volume
+ for _, v := range volumes {
+ newVol := Volume{
+ v,
+ }
+ vols = append(vols, &newVol)
+ }
+ return vols
+}
+
+// Build is the wrapper to build images
+func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, options imagebuildah.BuildOptions, dockerfiles []string) error {
+ namespaceOptions, networkPolicy, err := parse.NamespaceOptions(c.PodmanCommand.Command)
+ if err != nil {
+ return errors.Wrapf(err, "error parsing namespace-related options")
+ }
+ usernsOption, idmappingOptions, err := parse.IDMappingOptions(c.PodmanCommand.Command)
+ if err != nil {
+ return errors.Wrapf(err, "error parsing ID mapping options")
+ }
+ namespaceOptions.AddOrReplace(usernsOption...)
+
+ systemContext, err := parse.SystemContextFromOptions(c.PodmanCommand.Command)
+ if err != nil {
+ return errors.Wrapf(err, "error building system context")
+ }
+
+ authfile := c.Authfile
+ if len(c.Authfile) == 0 {
+ authfile = os.Getenv("REGISTRY_AUTH_FILE")
+ }
+
+ systemContext.AuthFilePath = authfile
+ commonOpts, err := parse.CommonBuildOptions(c.PodmanCommand.Command)
+ if err != nil {
+ return err
+ }
+
+ options.NamespaceOptions = namespaceOptions
+ options.ConfigureNetwork = networkPolicy
+ options.IDMappingOptions = idmappingOptions
+ options.CommonBuildOpts = commonOpts
+ options.SystemContext = systemContext
+
+ if c.Flag("runtime").Changed {
+ options.Runtime = r.GetOCIRuntimePath()
+ }
+ if c.Quiet {
+ options.ReportWriter = ioutil.Discard
+ }
+
+ if rootless.IsRootless() {
+ options.Isolation = buildah.IsolationOCIRootless
+ }
+
+ return r.Runtime.Build(ctx, options, dockerfiles...)
+}
diff --git a/libpod/adapter/runtime_remote.go b/libpod/adapter/runtime_remote.go
index f754aaee6..a96676ee2 100644
--- a/libpod/adapter/runtime_remote.go
+++ b/libpod/adapter/runtime_remote.go
@@ -7,18 +7,22 @@ import (
"context"
"encoding/json"
"fmt"
- "github.com/pkg/errors"
"io"
+ "io/ioutil"
"os"
"strings"
"time"
+ "github.com/containers/buildah/imagebuildah"
+ "github.com/containers/image/docker/reference"
"github.com/containers/image/types"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
+ "github.com/containers/storage/pkg/archive"
"github.com/opencontainers/go-digest"
+ "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/varlink/go/varlink"
)
@@ -91,6 +95,18 @@ type remoteContainer struct {
state *libpod.ContainerState
}
+type VolumeFilter func(*Volume) bool
+
+// Volume is embed for libpod volumes
+type Volume struct {
+ remoteVolume
+}
+
+type remoteVolume struct {
+ Runtime *LocalRuntime
+ config *libpod.VolumeConfig
+}
+
// GetImages returns a slice of containerimages over a varlink connection
func (r *LocalRuntime) GetImages() ([]*ContainerImage, error) {
var newImages []*ContainerImage
@@ -112,8 +128,8 @@ func (r *LocalRuntime) GetImages() ([]*ContainerImage, error) {
return newImages, nil
}
-func imageInListToContainerImage(i iopodman.ImageInList, name string, runtime *LocalRuntime) (*ContainerImage, error) {
- created, err := splitStringDate(i.Created)
+func imageInListToContainerImage(i iopodman.Image, name string, runtime *LocalRuntime) (*ContainerImage, error) {
+ created, err := time.ParseInLocation(time.RFC3339, i.Created, time.UTC)
if err != nil {
return nil, err
}
@@ -182,12 +198,6 @@ func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authf
return newImage, nil
}
-func splitStringDate(d string) (time.Time, error) {
- fields := strings.Fields(d)
- t := fmt.Sprintf("%sT%sZ", fields[0], fields[1])
- return time.ParseInLocation(time.RFC3339Nano, t, time.UTC)
-}
-
// IsParent goes through the layers in the store and checks if i.TopLayer is
// the parent of any other layer in store. Double check that image with that
// layer exists as well.
@@ -251,7 +261,7 @@ func (ci *ContainerImage) History(ctx context.Context) ([]*image.History, error)
return nil, err
}
for _, h := range reply {
- created, err := splitStringDate(h.Created)
+ created, err := time.ParseInLocation(time.RFC3339, h.Created, time.UTC)
if err != nil {
return nil, err
}
@@ -369,6 +379,108 @@ func (r *LocalRuntime) Export(name string, path string) error {
// Import implements the remote calls required to import a container image to the store
func (r *LocalRuntime) Import(ctx context.Context, source, reference string, changes []string, history string, quiet bool) (string, error) {
// First we send the file to the host
+ tempFile, err := r.SendFileOverVarlink(source)
+ if err != nil {
+ return "", err
+ }
+ return iopodman.ImportImage().Call(r.Conn, strings.TrimRight(tempFile, ":"), reference, history, changes, true)
+}
+
+func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, options imagebuildah.BuildOptions, dockerfiles []string) error {
+ buildOptions := iopodman.BuildOptions{
+ AddHosts: options.CommonBuildOpts.AddHost,
+ CgroupParent: options.CommonBuildOpts.CgroupParent,
+ CpuPeriod: int64(options.CommonBuildOpts.CPUPeriod),
+ CpuQuota: options.CommonBuildOpts.CPUQuota,
+ CpuShares: int64(options.CommonBuildOpts.CPUShares),
+ CpusetCpus: options.CommonBuildOpts.CPUSetMems,
+ CpusetMems: options.CommonBuildOpts.CPUSetMems,
+ Memory: options.CommonBuildOpts.Memory,
+ MemorySwap: options.CommonBuildOpts.MemorySwap,
+ ShmSize: options.CommonBuildOpts.ShmSize,
+ Ulimit: options.CommonBuildOpts.Ulimit,
+ Volume: options.CommonBuildOpts.Volumes,
+ }
+
+ buildinfo := iopodman.BuildInfo{
+ AdditionalTags: options.AdditionalTags,
+ Annotations: options.Annotations,
+ BuildArgs: options.Args,
+ BuildOptions: buildOptions,
+ CniConfigDir: options.CNIConfigDir,
+ CniPluginDir: options.CNIPluginPath,
+ Compression: string(options.Compression),
+ DefaultsMountFilePath: options.DefaultMountsFilePath,
+ Dockerfiles: dockerfiles,
+ //Err: string(options.Err),
+ ForceRmIntermediateCtrs: options.ForceRmIntermediateCtrs,
+ Iidfile: options.IIDFile,
+ Label: options.Labels,
+ Layers: options.Layers,
+ Nocache: options.NoCache,
+ //Out:
+ Output: options.Output,
+ OutputFormat: options.OutputFormat,
+ PullPolicy: options.PullPolicy.String(),
+ Quiet: options.Quiet,
+ RemoteIntermediateCtrs: options.RemoveIntermediateCtrs,
+ //ReportWriter:
+ RuntimeArgs: options.RuntimeArgs,
+ SignaturePolicyPath: options.SignaturePolicyPath,
+ Squash: options.Squash,
+ }
+ // tar the file
+ logrus.Debugf("creating tarball of context dir %s", options.ContextDirectory)
+ input, err := archive.Tar(options.ContextDirectory, archive.Uncompressed)
+ if err != nil {
+ return errors.Wrapf(err, "unable to create tarball of context dir %s", options.ContextDirectory)
+ }
+
+ // Write the tarball to the fs
+ // TODO we might considering sending this without writing to the fs for the sake of performance
+ // under given conditions like memory availability.
+ outputFile, err := ioutil.TempFile("", "varlink_tar_send")
+ if err != nil {
+ return err
+ }
+ defer outputFile.Close()
+ logrus.Debugf("writing context dir tarball to %s", outputFile.Name())
+
+ _, err = io.Copy(outputFile, input)
+ if err != nil {
+ return err
+ }
+
+ logrus.Debugf("completed writing context dir tarball %s", outputFile.Name())
+ // Send the context dir tarball over varlink.
+ tempFile, err := r.SendFileOverVarlink(outputFile.Name())
+ if err != nil {
+ return err
+ }
+ buildinfo.ContextDir = strings.Replace(tempFile, ":", "", -1)
+
+ reply, err := iopodman.BuildImage().Send(r.Conn, varlink.More, buildinfo)
+ if err != nil {
+ return err
+ }
+
+ for {
+ responses, flags, err := reply()
+ if err != nil {
+ return err
+ }
+ for _, line := range responses.Logs {
+ fmt.Print(line)
+ }
+ if flags&varlink.Continues == 0 {
+ break
+ }
+ }
+ return err
+}
+
+// SendFileOverVarlink sends a file over varlink in an upgraded connection
+func (r *LocalRuntime) SendFileOverVarlink(source string) (string, error) {
fs, err := os.Open(source)
if err != nil {
return "", err
@@ -378,6 +490,7 @@ func (r *LocalRuntime) Import(ctx context.Context, source, reference string, cha
if err != nil {
return "", err
}
+ logrus.Debugf("sending %s over varlink connection", source)
reply, err := iopodman.SendFile().Send(r.Conn, varlink.Upgrade, "", int64(fileInfo.Size()))
if err != nil {
return "", err
@@ -392,6 +505,7 @@ func (r *LocalRuntime) Import(ctx context.Context, source, reference string, cha
if err != nil {
return "", err
}
+ logrus.Debugf("file transfer complete for %s", source)
r.Conn.Writer.Flush()
// All was sent, wait for the ACK from the server
@@ -405,7 +519,8 @@ func (r *LocalRuntime) Import(ctx context.Context, source, reference string, cha
return "", err
}
- return iopodman.ImportImage().Call(r.Conn, strings.TrimRight(tempFile, ":"), reference, history, changes, true)
+
+ return tempFile, nil
}
// GetAllVolumes retrieves all the volumes
@@ -429,6 +544,101 @@ func (r *LocalRuntime) GetContainers(filters ...libpod.ContainerFilter) ([]*libp
// RemoveContainer removes the given container
// If force is specified, the container will be stopped first
// Otherwise, RemoveContainer will return an error if the container is running
-func (r *LocalRuntime) RemoveContainer(ctx context.Context, c *libpod.Container, force bool) error {
+func (r *LocalRuntime) RemoveContainer(ctx context.Context, c *libpod.Container, force, volumes bool) error {
return libpod.ErrNotImplemented
}
+
+// CreateVolume creates a volume over a varlink connection for the remote client
+func (r *LocalRuntime) CreateVolume(ctx context.Context, c *cliconfig.VolumeCreateValues, labels, opts map[string]string) (string, error) {
+ cvOpts := iopodman.VolumeCreateOpts{
+ Options: opts,
+ Labels: labels,
+ }
+ if len(c.InputArgs) > 0 {
+ cvOpts.VolumeName = c.InputArgs[0]
+ }
+
+ if c.Flag("driver").Changed {
+ cvOpts.Driver = c.Driver
+ }
+
+ return iopodman.VolumeCreate().Call(r.Conn, cvOpts)
+}
+
+// RemoveVolumes removes volumes over a varlink connection for the remote client
+func (r *LocalRuntime) RemoveVolumes(ctx context.Context, c *cliconfig.VolumeRmValues) ([]string, error) {
+ rmOpts := iopodman.VolumeRemoveOpts{
+ All: c.All,
+ Force: c.Force,
+ Volumes: c.InputArgs,
+ }
+ return iopodman.VolumeRemove().Call(r.Conn, rmOpts)
+}
+
+func (r *LocalRuntime) Push(ctx context.Context, srcName, destination, manifestMIMEType, authfile, signaturePolicyPath string, writer io.Writer, forceCompress bool, signingOptions image.SigningOptions, dockerRegistryOptions *image.DockerRegistryOptions, additionalDockerArchiveTags []reference.NamedTagged) error {
+
+ tls := true
+ if dockerRegistryOptions.DockerInsecureSkipTLSVerify == types.OptionalBoolTrue {
+ tls = false
+ }
+ reply, err := iopodman.PushImage().Send(r.Conn, varlink.More, srcName, destination, tls, signaturePolicyPath, "", dockerRegistryOptions.DockerCertPath, forceCompress, manifestMIMEType, signingOptions.RemoveSignatures, signingOptions.SignBy)
+ if err != nil {
+ return err
+ }
+ for {
+ responses, flags, err := reply()
+ if err != nil {
+ return err
+ }
+ for _, line := range responses.Logs {
+ fmt.Print(line)
+ }
+ if flags&varlink.Continues == 0 {
+ break
+ }
+ }
+
+ return err
+}
+
+// InspectVolumes returns a slice of volumes based on an arg list or --all
+func (r *LocalRuntime) InspectVolumes(ctx context.Context, c *cliconfig.VolumeInspectValues) ([]*Volume, error) {
+ reply, err := iopodman.GetVolumes().Call(r.Conn, c.InputArgs, c.All)
+ if err != nil {
+ return nil, err
+ }
+ return varlinkVolumeToVolume(r, reply), nil
+}
+
+//Volumes returns a slice of adapter.volumes based on information about libpod
+// volumes over a varlink connection
+func (r *LocalRuntime) Volumes(ctx context.Context) ([]*Volume, error) {
+ reply, err := iopodman.GetVolumes().Call(r.Conn, []string{}, true)
+ if err != nil {
+ return nil, err
+ }
+ return varlinkVolumeToVolume(r, reply), nil
+}
+
+func varlinkVolumeToVolume(r *LocalRuntime, volumes []iopodman.Volume) []*Volume {
+ var vols []*Volume
+ for _, v := range volumes {
+ volumeConfig := libpod.VolumeConfig{
+ Name: v.Name,
+ Labels: v.Labels,
+ MountPoint: v.MountPoint,
+ Driver: v.Driver,
+ Options: v.Options,
+ Scope: v.Scope,
+ }
+ n := remoteVolume{
+ Runtime: r,
+ config: &volumeConfig,
+ }
+ newVol := Volume{
+ n,
+ }
+ vols = append(vols, &newVol)
+ }
+ return vols
+}
diff --git a/libpod/adapter/volumes_remote.go b/libpod/adapter/volumes_remote.go
new file mode 100644
index 000000000..beacd943a
--- /dev/null
+++ b/libpod/adapter/volumes_remote.go
@@ -0,0 +1,33 @@
+// +build remoteclient
+
+package adapter
+
+// Name returns the name of the volume
+func (v *Volume) Name() string {
+ return v.config.Name
+}
+
+//Labels returns the labels for a volume
+func (v *Volume) Labels() map[string]string {
+ return v.config.Labels
+}
+
+// Driver returns the driver for the volume
+func (v *Volume) Driver() string {
+ return v.config.Driver
+}
+
+// Options returns the options a volume was created with
+func (v *Volume) Options() map[string]string {
+ return v.config.Options
+}
+
+// MountPath returns the path the volume is mounted to
+func (v *Volume) MountPoint() string {
+ return v.config.MountPoint
+}
+
+// Scope returns the scope for an adapter.volume
+func (v *Volume) Scope() string {
+ return v.config.Scope
+}
diff --git a/libpod/container.go b/libpod/container.go
index b0589be3b..75f4a4a4f 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -358,8 +358,7 @@ type ContainerConfig struct {
ExitCommand []string `json:"exitCommand,omitempty"`
// LocalVolumes are the built-in volumes we get from the --volumes-from flag
// It picks up the built-in volumes of the container used by --volumes-from
- LocalVolumes []string
-
+ LocalVolumes []spec.Mount
// IsInfra is a bool indicating whether this container is an infra container used for
// sharing kernel namespaces in a pod
IsInfra bool `json:"pause"`
@@ -557,8 +556,16 @@ func (c *Container) NewNetNS() bool {
// PortMappings returns the ports that will be mapped into a container if
// a new network namespace is created
// If NewNetNS() is false, this value is unused
-func (c *Container) PortMappings() []ocicni.PortMapping {
- return c.config.PortMappings
+func (c *Container) PortMappings() ([]ocicni.PortMapping, error) {
+ // First check if the container belongs to a network namespace (like a pod)
+ if len(c.config.NetNsCtr) > 0 {
+ netNsCtr, err := c.runtime.LookupContainer(c.config.NetNsCtr)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to lookup network namespace for container %s", c.ID())
+ }
+ return netNsCtr.PortMappings()
+ }
+ return c.config.PortMappings, nil
}
// DNSServers returns DNS servers that will be used in the container's
diff --git a/libpod/container_attach_linux.go b/libpod/container_attach_linux.go
index 1d6f0bd96..3ff6ddc76 100644
--- a/libpod/container_attach_linux.go
+++ b/libpod/container_attach_linux.go
@@ -109,8 +109,8 @@ func (c *Container) attachContainerSocket(resize <-chan remotecommand.TerminalSi
case err := <-receiveStdoutError:
return err
case err := <-stdinDone:
- if _, ok := err.(utils.DetachError); ok {
- return nil
+ if err == ErrDetach {
+ return err
}
if streams.AttachOutput || streams.AttachError {
return <-receiveStdoutError
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index b0dcc853e..b2ebad777 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -10,21 +10,16 @@ import (
"path/filepath"
"strconv"
"strings"
- "syscall"
"time"
- "github.com/containers/buildah/imagebuildah"
"github.com/containers/libpod/pkg/ctime"
"github.com/containers/libpod/pkg/hooks"
"github.com/containers/libpod/pkg/hooks/exec"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
- "github.com/containers/storage/pkg/chrootarchive"
"github.com/containers/storage/pkg/mount"
- "github.com/opencontainers/runc/libcontainer/user"
spec "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -489,9 +484,20 @@ func (c *Container) removeConmonFiles() error {
return errors.Wrapf(err, "error removing container %s OOM file", c.ID())
}
+ // Instead of outright deleting the exit file, rename it (if it exists).
+ // We want to retain it so we can get the exit code of containers which
+ // are removed (at least until we have a workable events system)
exitFile := filepath.Join(c.runtime.ociRuntime.exitsDir, c.ID())
- if err := os.Remove(exitFile); err != nil && !os.IsNotExist(err) {
- return errors.Wrapf(err, "error removing container %s exit file", c.ID())
+ oldExitFile := filepath.Join(c.runtime.ociRuntime.exitsDir, fmt.Sprintf("%s-old", c.ID()))
+ if _, err := os.Stat(exitFile); err != nil {
+ if !os.IsNotExist(err) {
+ return errors.Wrapf(err, "error running stat on container %s exit file", c.ID())
+ }
+ } else if err == nil {
+ // Rename should replace the old exit file (if it exists)
+ if err := os.Rename(exitFile, oldExitFile); err != nil {
+ return errors.Wrapf(err, "error renaming container %s exit file", c.ID())
+ }
}
return nil
@@ -1042,113 +1048,6 @@ func (c *Container) writeStringToRundir(destFile, output string) (string, error)
return filepath.Join(c.state.DestinationRunDir, destFile), nil
}
-func (c *Container) addLocalVolumes(ctx context.Context, g *generate.Generator, execUser *user.ExecUser) error {
- var uid, gid int
- mountPoint := c.state.Mountpoint
- if !c.state.Mounted {
- return errors.Wrapf(ErrInternal, "container is not mounted")
- }
- newImage, err := c.runtime.imageRuntime.NewFromLocal(c.config.RootfsImageID)
- if err != nil {
- return err
- }
- imageData, err := newImage.Inspect(ctx)
- if err != nil {
- return err
- }
- // Add the built-in volumes of the container passed in to --volumes-from
- for _, vol := range c.config.LocalVolumes {
- if imageData.Config.Volumes == nil {
- imageData.Config.Volumes = map[string]struct{}{
- vol: {},
- }
- } else {
- imageData.Config.Volumes[vol] = struct{}{}
- }
- }
-
- if c.config.User != "" {
- if execUser == nil {
- return errors.Wrapf(ErrInternal, "nil pointer passed to addLocalVolumes for execUser")
- }
- uid = execUser.Uid
- gid = execUser.Gid
- }
-
- for k := range imageData.Config.Volumes {
- mount := spec.Mount{
- Destination: k,
- Type: "bind",
- Options: []string{"private", "bind", "rw"},
- }
- if MountExists(g.Mounts(), k) {
- continue
- }
- volumePath := filepath.Join(c.config.StaticDir, "volumes", k)
-
- // Ensure the symlinks are resolved
- resolvedSymlink, err := imagebuildah.ResolveSymLink(mountPoint, k)
- if err != nil {
- return errors.Wrapf(ErrCtrStateInvalid, "cannot resolve %s in %s for container %s", k, mountPoint, c.ID())
- }
- var srcPath string
- if resolvedSymlink != "" {
- srcPath = filepath.Join(mountPoint, resolvedSymlink)
- } else {
- srcPath = filepath.Join(mountPoint, k)
- }
-
- if _, err := os.Stat(srcPath); os.IsNotExist(err) {
- logrus.Infof("Volume image mount point %s does not exist in root FS, need to create it", k)
- if err = os.MkdirAll(srcPath, 0755); err != nil {
- return errors.Wrapf(err, "error creating directory %q for volume %q in container %q", volumePath, k, c.ID())
- }
-
- if err = os.Chown(srcPath, uid, gid); err != nil {
- return errors.Wrapf(err, "error chowning directory %q for volume %q in container %q", srcPath, k, c.ID())
- }
- }
-
- if _, err := os.Stat(volumePath); os.IsNotExist(err) {
- if err = os.MkdirAll(volumePath, 0755); err != nil {
- return errors.Wrapf(err, "error creating directory %q for volume %q in container %q", volumePath, k, c.ID())
- }
-
- if err = os.Chown(volumePath, uid, gid); err != nil {
- return errors.Wrapf(err, "error chowning directory %q for volume %q in container %q", volumePath, k, c.ID())
- }
-
- if err = label.Relabel(volumePath, c.config.MountLabel, false); err != nil {
- return errors.Wrapf(err, "error relabeling directory %q for volume %q in container %q", volumePath, k, c.ID())
- }
- if err = chrootarchive.NewArchiver(nil).CopyWithTar(srcPath, volumePath); err != nil && !os.IsNotExist(err) {
- return errors.Wrapf(err, "error populating directory %q for volume %q in container %q using contents of %q", volumePath, k, c.ID(), srcPath)
- }
-
- // Set the volume path with the same owner and permission of source path
- sstat, _ := os.Stat(srcPath)
- st, ok := sstat.Sys().(*syscall.Stat_t)
- if !ok {
- return fmt.Errorf("could not convert to syscall.Stat_t")
- }
- uid := int(st.Uid)
- gid := int(st.Gid)
-
- if err := os.Lchown(volumePath, uid, gid); err != nil {
- return err
- }
- if os.Chmod(volumePath, sstat.Mode()); err != nil {
- return err
- }
-
- }
-
- mount.Source = volumePath
- g.AddMount(mount)
- }
- return nil
-}
-
// Save OCI spec to disk, replacing any existing specs for the container
func (c *Container) saveSpec(spec *spec.Spec) error {
// If the OCI spec already exists, we need to replace it
@@ -1292,3 +1191,30 @@ func getExcludedCGroups() (excludes []string) {
excludes = []string{"rdma"}
return
}
+
+// namedVolumes returns named volumes for the container
+func (c *Container) namedVolumes() ([]string, error) {
+ var volumes []string
+ for _, vol := range c.config.Spec.Mounts {
+ if strings.HasPrefix(vol.Source, c.runtime.config.VolumePath) {
+ volume := strings.TrimPrefix(vol.Source, c.runtime.config.VolumePath+"/")
+ split := strings.Split(volume, "/")
+ volume = split[0]
+ if _, err := c.runtime.state.Volume(volume); err == nil {
+ volumes = append(volumes, volume)
+ }
+ }
+ }
+ return volumes, nil
+}
+
+// this should be from chrootarchive.
+func (c *Container) copyWithTarFromImage(src, dest string) error {
+ mountpoint, err := c.mount()
+ if err != nil {
+ return err
+ }
+ a := archive.NewDefaultArchiver()
+ source := filepath.Join(mountpoint, src)
+ return a.CopyWithTar(source, dest)
+}
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index bcdfdaee3..65cb47c8c 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -235,13 +235,6 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
}
}
- // Bind builtin image volumes
- if c.config.Rootfs == "" && c.config.ImageVolumes {
- if err := c.addLocalVolumes(ctx, &g, execUser); err != nil {
- return nil, errors.Wrapf(err, "error mounting image volumes")
- }
- }
-
if c.config.User != "" {
// User and Group must go together
g.SetProcessUID(uint32(execUser.Uid))
diff --git a/libpod/errors.go b/libpod/errors.go
index d6614141c..dd82d0796 100644
--- a/libpod/errors.go
+++ b/libpod/errors.go
@@ -2,15 +2,21 @@ package libpod
import (
"errors"
+
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/utils"
)
var (
// ErrNoSuchCtr indicates the requested container does not exist
- ErrNoSuchCtr = errors.New("no such container")
+ ErrNoSuchCtr = image.ErrNoSuchCtr
+
// ErrNoSuchPod indicates the requested pod does not exist
- ErrNoSuchPod = errors.New("no such pod")
+ ErrNoSuchPod = image.ErrNoSuchPod
+
// ErrNoSuchImage indicates the requested image does not exist
- ErrNoSuchImage = errors.New("no such image")
+ ErrNoSuchImage = image.ErrNoSuchImage
+
// ErrNoSuchVolume indicates the requested volume does not exist
ErrNoSuchVolume = errors.New("no such volume")
@@ -51,6 +57,10 @@ var (
// ErrInternal indicates an internal library error
ErrInternal = errors.New("internal libpod error")
+ // ErrDetach indicates that an attach session was manually detached by
+ // the user.
+ ErrDetach = utils.ErrDetach
+
// ErrRuntimeStopped indicates that the runtime has already been shut
// down and no further operations can be performed on it
ErrRuntimeStopped = errors.New("runtime has already been stopped")
diff --git a/libpod/image/utils.go b/libpod/image/utils.go
index ad027f32a..3585428ad 100644
--- a/libpod/image/utils.go
+++ b/libpod/image/utils.go
@@ -87,22 +87,29 @@ func hasTransport(image string) bool {
// ReposToMap parses the specified repotags and returns a map with repositories
// as keys and the corresponding arrays of tags as values.
-func ReposToMap(repotags []string) map[string][]string {
+func ReposToMap(repotags []string) (map[string][]string, error) {
// map format is repo -> tag
repos := make(map[string][]string)
for _, repo := range repotags {
var repository, tag string
if len(repo) > 0 {
- li := strings.LastIndex(repo, ":")
- repository = repo[0:li]
- tag = repo[li+1:]
+ named, err := reference.ParseNormalizedNamed(repo)
+ repository = named.Name()
+ if err != nil {
+ return nil, err
+ }
+ if ref, ok := named.(reference.NamedTagged); ok {
+ tag = ref.Tag()
+ } else if ref, ok := named.(reference.Canonical); ok {
+ tag = ref.Digest().String()
+ }
}
repos[repository] = append(repos[repository], tag)
}
if len(repos) == 0 {
repos["<none>"] = []string{"<none>"}
}
- return repos
+ return repos, nil
}
// GetAdditionalTags returns a list of reference.NamedTagged for the
diff --git a/libpod/kube.go b/libpod/kube.go
index 16cebf99b..484127870 100644
--- a/libpod/kube.go
+++ b/libpod/kube.go
@@ -228,7 +228,11 @@ func containerToV1Container(c *Container) (v1.Container, error) {
return kubeContainer, nil
}
- ports, err := ocicniPortMappingToContainerPort(c.PortMappings())
+ portmappings, err := c.PortMappings()
+ if err != nil {
+ return kubeContainer, err
+ }
+ ports, err := ocicniPortMappingToContainerPort(portmappings)
if err != nil {
return kubeContainer, nil
}
diff --git a/libpod/options.go b/libpod/options.go
index d965c058e..06737776b 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -11,6 +11,7 @@ import (
"github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
"github.com/cri-o/ocicni/pkg/ocicni"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
@@ -1058,7 +1059,7 @@ func WithUserVolumes(volumes []string) CtrCreateOption {
// from a container passed in to the --volumes-from flag.
// This stores the built-in volume information in the Config so we can
// add them when creating the container.
-func WithLocalVolumes(volumes []string) CtrCreateOption {
+func WithLocalVolumes(volumes []spec.Mount) CtrCreateOption {
return func(ctr *Container) error {
if ctr.valid {
return ErrCtrFinalized
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index 9afdef7b6..185090cf7 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -10,7 +10,9 @@ import (
"strings"
"time"
+ "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/rootless"
+ "github.com/containers/storage"
"github.com/containers/storage/pkg/stringid"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
@@ -175,9 +177,12 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options ..
if err != nil {
newVol, err := r.newVolume(ctx, WithVolumeName(vol.Source))
if err != nil {
- logrus.Errorf("error creating named volume %q: %v", vol.Source, err)
+ return nil, errors.Wrapf(err, "error creating named volume %q", vol.Source)
}
ctr.config.Spec.Mounts[i].Source = newVol.MountPoint()
+ if err := ctr.copyWithTarFromImage(ctr.config.Spec.Mounts[i].Destination, ctr.config.Spec.Mounts[i].Source); err != nil && !os.IsNotExist(err) {
+ return nil, errors.Wrapf(err, "Failed to copy content into new volume mount %q", vol.Source)
+ }
continue
}
ctr.config.Spec.Mounts[i].Source = volInfo.MountPoint()
@@ -223,17 +228,19 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options ..
// RemoveContainer removes the given container
// If force is specified, the container will be stopped first
+// If removeVolume is specified, named volumes used by the container will
+// be removed also if and only if the container is the sole user
// Otherwise, RemoveContainer will return an error if the container is running
-func (r *Runtime) RemoveContainer(ctx context.Context, c *Container, force bool) error {
+func (r *Runtime) RemoveContainer(ctx context.Context, c *Container, force bool, removeVolume bool) error {
r.lock.Lock()
defer r.lock.Unlock()
- return r.removeContainer(ctx, c, force)
+ return r.removeContainer(ctx, c, force, removeVolume)
}
// Internal function to remove a container
// Locks the container, but does not lock the runtime
-func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool) error {
+func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, removeVolume bool) error {
if !c.valid {
if ok, _ := r.state.HasContainer(c.ID()); !ok {
// Container probably already removed
@@ -246,6 +253,7 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool)
// To avoid races around removing a container and the pod it is in
var pod *Pod
var err error
+ runtime := c.runtime
if c.config.Pod != "" {
pod, err = r.state.Pod(c.config.Pod)
if err != nil {
@@ -331,6 +339,13 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool)
return errors.Wrapf(ErrCtrExists, "container %s has dependent containers which must be removed before it: %s", c.ID(), depsStr)
}
+ var volumes []string
+ if removeVolume {
+ volumes, err = c.namedVolumes()
+ if err != nil {
+ logrus.Errorf("unable to retrieve builtin volumes for container %v: %v", c.ID(), err)
+ }
+ }
var cleanupErr error
// Remove the container from the state
if c.config.Pod != "" {
@@ -395,6 +410,14 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool)
}
}
+ for _, v := range volumes {
+ if volume, err := runtime.state.Volume(v); err == nil {
+ if err := runtime.removeVolume(ctx, volume, false, true); err != nil && err != ErrNoSuchVolume && err != ErrVolumeBeingUsed {
+ logrus.Errorf("cleanup volume (%s): %v", v, err)
+ }
+ }
+ }
+
return cleanupErr
}
@@ -564,3 +587,16 @@ func (r *Runtime) Export(name string, path string) error {
return ctr.Export(path)
}
+
+// RemoveContainersFromStorage attempt to remove containers from storage that do not exist in libpod database
+func (r *Runtime) RemoveContainersFromStorage(ctrs []string) {
+ for _, i := range ctrs {
+ // if the container does not exist in database, attempt to remove it from storage
+ if _, err := r.LookupContainer(i); err != nil && errors.Cause(err) == image.ErrNoSuchCtr {
+ r.storageService.UnmountContainerImage(i, true)
+ if err := r.storageService.DeleteContainer(i); err != nil && errors.Cause(err) != storage.ErrContainerUnknown {
+ logrus.Errorf("Failed to remove container %q from storage: %s", i, err)
+ }
+ }
+ }
+}
diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go
index c20aa77a3..1e9689362 100644
--- a/libpod/runtime_img.go
+++ b/libpod/runtime_img.go
@@ -43,7 +43,7 @@ func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool)
if len(imageCtrs) > 0 && len(img.Names()) <= 1 {
if force {
for _, ctr := range imageCtrs {
- if err := r.removeContainer(ctx, ctr, true); err != nil {
+ if err := r.removeContainer(ctx, ctr, true, false); err != nil {
return "", errors.Wrapf(err, "error removing image %s: container %s using image could not be removed", img.ID(), ctr.ID())
}
}
diff --git a/libpod/runtime_volume.go b/libpod/runtime_volume.go
index 3921758ee..485f64bf1 100644
--- a/libpod/runtime_volume.go
+++ b/libpod/runtime_volume.go
@@ -2,6 +2,9 @@ package libpod
import (
"context"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "strings"
)
// Contains the public Runtime API for volumes
@@ -38,6 +41,38 @@ func (r *Runtime) RemoveVolume(ctx context.Context, v *Volume, force, prune bool
return r.removeVolume(ctx, v, force, prune)
}
+// RemoveVolumes removes a slice of volumes or all with a force bool
+func (r *Runtime) RemoveVolumes(ctx context.Context, volumes []string, all, force bool) ([]string, error) {
+ var (
+ vols []*Volume
+ err error
+ deletedVols []string
+ )
+ if all {
+ vols, err = r.Volumes()
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to get all volumes")
+ }
+ } else {
+ for _, i := range volumes {
+ vol, err := r.GetVolume(i)
+ if err != nil {
+ return nil, err
+ }
+ vols = append(vols, vol)
+ }
+ }
+
+ for _, vol := range vols {
+ if err := r.RemoveVolume(ctx, vol, force, false); err != nil {
+ return deletedVols, err
+ }
+ logrus.Debugf("removed volume %s", vol.Name())
+ deletedVols = append(deletedVols, vol.Name())
+ }
+ return deletedVols, nil
+}
+
// GetVolume retrieves a volume by its name
func (r *Runtime) GetVolume(name string) (*Volume, error) {
r.lock.RLock()
@@ -47,7 +82,21 @@ func (r *Runtime) GetVolume(name string) (*Volume, error) {
return nil, ErrRuntimeStopped
}
- return r.state.Volume(name)
+ vol, err := r.state.Volume(name)
+ if err == nil {
+ return vol, err
+ }
+
+ vols, err := r.GetAllVolumes()
+ if err != nil {
+ return nil, err
+ }
+ for _, v := range vols {
+ if strings.HasPrefix(v.Name(), name) {
+ return v, nil
+ }
+ }
+ return nil, errors.Errorf("unable to find volume %s", name)
}
// HasVolume checks to see if a volume with the given name exists
diff --git a/libpod/volume_internal.go b/libpod/volume_internal.go
index 800e6d106..0de8a2350 100644
--- a/libpod/volume_internal.go
+++ b/libpod/volume_internal.go
@@ -5,10 +5,6 @@ import (
"path/filepath"
)
-// VolumePath is the path under which all volumes that are created using the
-// local driver will be created
-// const VolumePath = "/var/lib/containers/storage/volumes"
-
// Creates a new volume
func newVolume(runtime *Runtime) (*Volume, error) {
volume := new(Volume)