summaryrefslogtreecommitdiff
path: root/cmd/podman
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/podman')
-rw-r--r--cmd/podman/container.go2
-rw-r--r--cmd/podman/containers_prune.go74
-rw-r--r--cmd/podman/create.go69
-rw-r--r--cmd/podman/create_cli.go11
-rw-r--r--cmd/podman/image.go2
-rw-r--r--cmd/podman/images_prune.go34
-rw-r--r--cmd/podman/libpodruntime/runtime.go36
-rw-r--r--cmd/podman/login.go48
-rw-r--r--cmd/podman/main.go12
-rw-r--r--cmd/podman/ps.go5
-rw-r--r--cmd/podman/pull.go10
-rw-r--r--cmd/podman/push.go16
-rw-r--r--cmd/podman/rmi.go19
-rw-r--r--cmd/podman/run.go2
-rw-r--r--cmd/podman/runlabel.go80
-rw-r--r--cmd/podman/save.go2
-rw-r--r--cmd/podman/search.go70
-rw-r--r--cmd/podman/shared/container.go92
-rw-r--r--cmd/podman/shared/prune.go24
-rw-r--r--cmd/podman/utils.go34
-rw-r--r--cmd/podman/varlink/io.podman.varlink55
-rw-r--r--cmd/podman/volume.go26
-rw-r--r--cmd/podman/volume_create.go97
-rw-r--r--cmd/podman/volume_inspect.go63
-rw-r--r--cmd/podman/volume_ls.go308
-rw-r--r--cmd/podman/volume_prune.go86
-rw-r--r--cmd/podman/volume_rm.go71
27 files changed, 1146 insertions, 202 deletions
diff --git a/cmd/podman/container.go b/cmd/podman/container.go
index b6262f890..b0232c874 100644
--- a/cmd/podman/container.go
+++ b/cmd/podman/container.go
@@ -22,7 +22,7 @@ var (
mountCommand,
pauseCommand,
portCommand,
- // pruneCommand,
+ pruneContainersCommand,
refreshCommand,
restartCommand,
restoreCommand,
diff --git a/cmd/podman/containers_prune.go b/cmd/podman/containers_prune.go
new file mode 100644
index 000000000..92604e82f
--- /dev/null
+++ b/cmd/podman/containers_prune.go
@@ -0,0 +1,74 @@
+package main
+
+import (
+ "github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/libpod"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/urfave/cli"
+)
+
+var (
+ pruneContainersDescription = `
+ podman container prune
+
+ Removes all exited containers
+`
+
+ pruneContainersCommand = cli.Command{
+ Name: "prune",
+ Usage: "Remove all stopped containers",
+ Description: pruneContainersDescription,
+ Action: pruneContainersCmd,
+ OnUsageError: usageErrorHandler,
+ }
+)
+
+func pruneContainersCmd(c *cli.Context) error {
+ var (
+ deleteFuncs []shared.ParallelWorkerInput
+ )
+
+ ctx := getContext()
+ runtime, err := libpodruntime.GetRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "could not get runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ filter := func(c *libpod.Container) bool {
+ state, _ := c.State()
+ if state == libpod.ContainerStateStopped || (state == libpod.ContainerStateExited && err == nil && c.PodID() == "") {
+ return true
+ }
+ return false
+ }
+ delContainers, err := runtime.GetContainers(filter)
+ if err != nil {
+ return err
+ }
+ if len(delContainers) < 1 {
+ return nil
+ }
+ for _, container := range delContainers {
+ con := container
+ f := func() error {
+ return runtime.RemoveContainer(ctx, con, c.Bool("force"))
+ }
+
+ deleteFuncs = append(deleteFuncs, shared.ParallelWorkerInput{
+ ContainerID: con.ID(),
+ ParallelFunc: f,
+ })
+ }
+ maxWorkers := shared.Parallelize("rm")
+ if c.GlobalIsSet("max-workers") {
+ maxWorkers = c.GlobalInt("max-workers")
+ }
+ logrus.Debugf("Setting maximum workers to %d", maxWorkers)
+
+ // Run the parallel funcs
+ deleteErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, deleteFuncs)
+ return printParallelOutput(deleteErrors, errCount)
+}
diff --git a/cmd/podman/create.go b/cmd/podman/create.go
index bcf830c7c..6c6bcfb41 100644
--- a/cmd/podman/create.go
+++ b/cmd/podman/create.go
@@ -11,6 +11,7 @@ import (
"syscall"
"github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
ann "github.com/containers/libpod/pkg/annotations"
@@ -66,7 +67,7 @@ func createCmd(c *cli.Context) error {
rootless.SetSkipStorageSetup(true)
}
- runtime, err := libpodruntime.GetContainerRuntime(c)
+ runtime, err := libpodruntime.GetRuntime(c)
if err != nil {
return errors.Wrapf(err, "error creating libpod runtime")
}
@@ -128,7 +129,7 @@ func createContainer(c *cli.Context, runtime *libpod.Runtime) (*libpod.Container
var data *inspect.ImageData = nil
if rootfs == "" && !rootless.SkipStorageSetup() {
- newImage, err := runtime.ImageRuntime().New(ctx, c.Args()[0], rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{}, false, false)
+ newImage, err := runtime.ImageRuntime().New(ctx, c.Args()[0], rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{}, false)
if err != nil {
return nil, nil, err
}
@@ -375,8 +376,8 @@ func configureEntrypoint(c *cli.Context, data *inspect.ImageData) []string {
return entrypoint
}
-func configurePod(c *cli.Context, runtime *libpod.Runtime, namespaces map[string]string) (map[string]string, error) {
- pod, err := runtime.LookupPod(c.String("pod"))
+func configurePod(c *cli.Context, runtime *libpod.Runtime, namespaces map[string]string, podName string) (map[string]string, error) {
+ pod, err := runtime.LookupPod(podName)
if err != nil {
return namespaces, err
}
@@ -409,6 +410,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim
inputCommand, command []string
memoryLimit, memoryReservation, memorySwap, memoryKernel int64
blkioWeight uint16
+ namespaces map[string]string
)
idmappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidname"), c.String("subgidname"))
if err != nil {
@@ -492,12 +494,21 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim
return nil, errors.Errorf("--cpu-quota and --cpus cannot be set together")
}
+ // EXPOSED PORTS
+ var portBindings map[nat.Port][]nat.PortBinding
+ if data != nil {
+ portBindings, err = cc.ExposedPorts(c.StringSlice("expose"), c.StringSlice("publish"), c.Bool("publish-all"), data.ContainerConfig.ExposedPorts)
+ if err != nil {
+ return nil, err
+ }
+ }
+
// Kernel Namespaces
// TODO Fix handling of namespace from pod
// Instead of integrating here, should be done in libpod
// However, that also involves setting up security opts
// when the pod's namespace is integrated
- namespaces := map[string]string{
+ namespaces = map[string]string{
"pid": c.String("pid"),
"net": c.String("net"),
"ipc": c.String("ipc"),
@@ -505,8 +516,41 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim
"uts": c.String("uts"),
}
+ originalPodName := c.String("pod")
+ podName := strings.Replace(originalPodName, "new:", "", 1)
+ // after we strip out :new, make sure there is something left for a pod name
+ if len(podName) < 1 && c.IsSet("pod") {
+ return nil, errors.Errorf("new pod name must be at least one character")
+ }
if c.IsSet("pod") {
- namespaces, err = configurePod(c, runtime, namespaces)
+ if strings.HasPrefix(originalPodName, "new:") {
+ // pod does not exist; lets make it
+ var podOptions []libpod.PodCreateOption
+ podOptions = append(podOptions, libpod.WithPodName(podName), libpod.WithInfraContainer(), libpod.WithPodCgroups())
+ if len(portBindings) > 0 {
+ ociPortBindings, err := cc.NatToOCIPortBindings(portBindings)
+ if err != nil {
+ return nil, err
+ }
+ podOptions = append(podOptions, libpod.WithInfraContainerPorts(ociPortBindings))
+ }
+
+ podNsOptions, err := shared.GetNamespaceOptions(strings.Split(DefaultKernelNamespaces, ","))
+ if err != nil {
+ return nil, err
+ }
+ podOptions = append(podOptions, podNsOptions...)
+ // make pod
+ pod, err := runtime.NewPod(ctx, podOptions...)
+ if err != nil {
+ return nil, err
+ }
+ logrus.Debugf("pod %s created by new container request", pod.ID())
+
+ // The container now cannot have port bindings; so we reset the map
+ portBindings = make(map[nat.Port][]nat.PortBinding)
+ }
+ namespaces, err = configurePod(c, runtime, namespaces, podName)
if err != nil {
return nil, err
}
@@ -535,7 +579,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim
// Make sure if network is set to container namespace, port binding is not also being asked for
netMode := ns.NetworkMode(namespaces["net"])
if netMode.IsContainer() {
- if len(c.StringSlice("publish")) > 0 || c.Bool("publish-all") {
+ if len(portBindings) > 0 {
return nil, errors.Errorf("cannot set port bindings on an existing container network namespace")
}
}
@@ -644,15 +688,6 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim
return nil, errors.Errorf("No command specified on command line or as CMD or ENTRYPOINT in this image")
}
- // EXPOSED PORTS
- var portBindings map[nat.Port][]nat.PortBinding
- if data != nil {
- portBindings, err = cc.ExposedPorts(c.StringSlice("expose"), c.StringSlice("publish"), c.Bool("publish-all"), data.ContainerConfig.ExposedPorts)
- if err != nil {
- return nil, err
- }
- }
-
// SHM Size
shmSize, err := units.FromHumanSize(c.String("shm-size"))
if err != nil {
@@ -746,7 +781,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim
NetMode: netMode,
UtsMode: utsMode,
PidMode: pidMode,
- Pod: c.String("pod"),
+ Pod: podName,
Privileged: c.Bool("privileged"),
Publish: c.StringSlice("publish"),
PublishAll: c.Bool("publish-all"),
diff --git a/cmd/podman/create_cli.go b/cmd/podman/create_cli.go
index 218e9b806..b3a30d185 100644
--- a/cmd/podman/create_cli.go
+++ b/cmd/podman/create_cli.go
@@ -201,12 +201,13 @@ func parseVolumesFrom(volumesFrom []string) error {
}
func validateVolumeHostDir(hostDir string) error {
- if !filepath.IsAbs(hostDir) {
- return errors.Errorf("invalid host path, must be an absolute path %q", hostDir)
- }
- if _, err := os.Stat(hostDir); err != nil {
- return errors.Wrapf(err, "error checking path %q", hostDir)
+ if filepath.IsAbs(hostDir) {
+ if _, err := os.Stat(hostDir); err != nil {
+ return errors.Wrapf(err, "error checking path %q", hostDir)
+ }
}
+ // If hostDir is not an absolute path, that means the user wants to create a
+ // named volume. This will be done later on in the code.
return nil
}
diff --git a/cmd/podman/image.go b/cmd/podman/image.go
index 418b442e3..95af36df5 100644
--- a/cmd/podman/image.go
+++ b/cmd/podman/image.go
@@ -13,7 +13,7 @@ var (
inspectCommand,
loadCommand,
lsImagesCommand,
- // pruneCommand,
+ pruneImagesCommand,
pullCommand,
pushCommand,
rmImageCommand,
diff --git a/cmd/podman/images_prune.go b/cmd/podman/images_prune.go
new file mode 100644
index 000000000..cb72a498f
--- /dev/null
+++ b/cmd/podman/images_prune.go
@@ -0,0 +1,34 @@
+package main
+
+import (
+ "github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/pkg/errors"
+ "github.com/urfave/cli"
+)
+
+var (
+ pruneImagesDescription = `
+ podman image prune
+
+ Removes all unnamed images from local storage
+`
+
+ pruneImagesCommand = cli.Command{
+ Name: "prune",
+ Usage: "Remove unused images",
+ Description: pruneImagesDescription,
+ Action: pruneImagesCmd,
+ OnUsageError: usageErrorHandler,
+ }
+)
+
+func pruneImagesCmd(c *cli.Context) error {
+ runtime, err := libpodruntime.GetRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "could not get runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ return shared.Prune(runtime.ImageRuntime())
+}
diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go
index a4b3581be..d7a0dd931 100644
--- a/cmd/podman/libpodruntime/runtime.go
+++ b/cmd/podman/libpodruntime/runtime.go
@@ -11,31 +11,22 @@ import (
// GetRuntime generates a new libpod runtime configured by command line options
func GetRuntime(c *cli.Context) (*libpod.Runtime, error) {
- storageOpts, err := util.GetDefaultStoreOptions()
- if err != nil {
- return nil, err
- }
- return GetRuntimeWithStorageOpts(c, &storageOpts)
-}
+ storageOpts := new(storage.StoreOptions)
+ options := []libpod.RuntimeOption{}
-// GetContainerRuntime generates a new libpod runtime configured by command line options for containers
-func GetContainerRuntime(c *cli.Context) (*libpod.Runtime, error) {
- mappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidmap"), c.String("subgidmap"))
+ _, volumePath, err := util.GetDefaultStoreOptions()
if err != nil {
return nil, err
}
- storageOpts, err := util.GetDefaultStoreOptions()
- if err != nil {
- return nil, err
- }
- storageOpts.UIDMap = mappings.UIDMap
- storageOpts.GIDMap = mappings.GIDMap
- return GetRuntimeWithStorageOpts(c, &storageOpts)
-}
-// GetRuntime generates a new libpod runtime configured by command line options
-func GetRuntimeWithStorageOpts(c *cli.Context, storageOpts *storage.StoreOptions) (*libpod.Runtime, error) {
- options := []libpod.RuntimeOption{}
+ if c.IsSet("uidmap") || c.IsSet("gidmap") || c.IsSet("subuidmap") || c.IsSet("subgidmap") {
+ mappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidmap"), c.String("subgidmap"))
+ if err != nil {
+ return nil, err
+ }
+ storageOpts.UIDMap = mappings.UIDMap
+ storageOpts.GIDMap = mappings.GIDMap
+ }
if c.GlobalIsSet("root") {
storageOpts.GraphRoot = c.GlobalString("root")
@@ -90,8 +81,8 @@ func GetRuntimeWithStorageOpts(c *cli.Context, storageOpts *storage.StoreOptions
if c.GlobalIsSet("default-mounts-file") {
options = append(options, libpod.WithDefaultMountsFile(c.GlobalString("default-mounts-file")))
}
- if c.GlobalIsSet("hooks-dir-path") {
- options = append(options, libpod.WithHooksDir(c.GlobalString("hooks-dir-path")))
+ if c.GlobalIsSet("hooks-dir") {
+ options = append(options, libpod.WithHooksDir(c.GlobalStringSlice("hooks-dir")...))
}
// TODO flag to set CNI plugins dir?
@@ -104,6 +95,7 @@ func GetRuntimeWithStorageOpts(c *cli.Context, storageOpts *storage.StoreOptions
if c.IsSet("infra-command") {
options = append(options, libpod.WithDefaultInfraCommand(c.String("infra-command")))
}
+ options = append(options, libpod.WithVolumePath(volumePath))
if c.IsSet("config") {
return libpod.NewRuntimeFromConfig(c.String("config"), options...)
}
diff --git a/cmd/podman/login.go b/cmd/podman/login.go
index aa26d1466..cfdd8005b 100644
--- a/cmd/podman/login.go
+++ b/cmd/podman/login.go
@@ -2,13 +2,13 @@ package main
import (
"bufio"
- "context"
"fmt"
"os"
"strings"
"github.com/containers/image/docker"
"github.com/containers/image/pkg/docker/config"
+ "github.com/containers/image/types"
"github.com/containers/libpod/libpod/common"
"github.com/pkg/errors"
"github.com/urfave/cli"
@@ -60,27 +60,50 @@ func loginCmd(c *cli.Context) error {
if len(args) == 0 {
return errors.Errorf("registry must be given")
}
- server := scrubServer(args[0])
+ server := registryFromFullName(scrubServer(args[0]))
authfile := getAuthFile(c.String("authfile"))
sc := common.GetSystemContext("", authfile, false)
// username of user logged in to server (if one exists)
- userFromAuthFile, err := config.GetUserLoggedIn(sc, server)
+ userFromAuthFile, passFromAuthFile, err := config.GetAuthentication(sc, server)
if err != nil {
return errors.Wrapf(err, "error getting logged-in user")
}
- username, password, err := getUserAndPass(c.String("username"), c.String("password"), userFromAuthFile)
+
+ ctx := getContext()
+
+ var (
+ username string
+ password string
+ )
+
+ if userFromAuthFile != "" {
+ username = userFromAuthFile
+ password = passFromAuthFile
+ fmt.Println("Authenticating with existing credentials...")
+ if err := docker.CheckAuth(ctx, sc, username, password, server); err == nil {
+ fmt.Println("Existing credentials are valid. Already logged in to", server)
+ return nil
+ }
+ fmt.Println("Existing credentials are invalid, please enter valid username and password")
+ }
+
+ username, password, err = getUserAndPass(c.String("username"), c.String("password"), userFromAuthFile)
if err != nil {
return errors.Wrapf(err, "error getting username and password")
}
- sc.DockerInsecureSkipTLSVerify = !c.BoolT("tls-verify")
+
+ if c.IsSet("tls-verify") {
+ sc.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify"))
+ }
if c.String("cert-dir") != "" {
sc.DockerCertPath = c.String("cert-dir")
}
- if err = docker.CheckAuth(context.TODO(), sc, username, password, server); err == nil {
- if err := config.SetAuthentication(sc, server, username, password); err != nil {
+ if err = docker.CheckAuth(ctx, sc, username, password, server); err == nil {
+ // Write the new credentials to the authfile
+ if err = config.SetAuthentication(sc, server, username, password); err != nil {
return err
}
}
@@ -126,3 +149,14 @@ func getUserAndPass(username, password, userFromAuthFile string) (string, string
}
return strings.TrimSpace(username), password, err
}
+
+// registryFromFullName gets the registry from the input. If the input is of the form
+// quay.io/myuser/myimage, it will parse it and just return quay.io
+// It also returns true if a full image name was given
+func registryFromFullName(input string) string {
+ split := strings.Split(input, "/")
+ if len(split) > 1 {
+ return split[0]
+ }
+ return split[0]
+}
diff --git a/cmd/podman/main.go b/cmd/podman/main.go
index 6be192593..280448dc8 100644
--- a/cmd/podman/main.go
+++ b/cmd/podman/main.go
@@ -2,13 +2,13 @@ package main
import (
"fmt"
+ "log/syslog"
"os"
"os/exec"
"runtime/pprof"
"syscall"
"github.com/containers/libpod/libpod"
- "github.com/containers/libpod/pkg/hooks"
_ "github.com/containers/libpod/pkg/hooks/0.1.0"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/version"
@@ -17,7 +17,6 @@ import (
"github.com/sirupsen/logrus"
lsyslog "github.com/sirupsen/logrus/hooks/syslog"
"github.com/urfave/cli"
- "log/syslog"
)
// This is populated by the Makefile from the VERSION file
@@ -103,6 +102,7 @@ func main() {
umountCommand,
unpauseCommand,
versionCommand,
+ volumeCommand,
waitCommand,
}
@@ -206,11 +206,9 @@ func main() {
Usage: "path to default mounts file",
Hidden: true,
},
- cli.StringFlag{
- Name: "hooks-dir-path",
- Usage: "set the OCI hooks directory path",
- Value: hooks.DefaultDir,
- Hidden: true,
+ cli.StringSliceFlag{
+ Name: "hooks-dir",
+ Usage: "set the OCI hooks directory path (may be set multiple times)",
},
cli.IntFlag{
Name: "max-workers",
diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go
index 0b03388a2..7a4a80769 100644
--- a/cmd/podman/ps.go
+++ b/cmd/podman/ps.go
@@ -200,6 +200,10 @@ var (
Usage: "Sort output by command, created, id, image, names, runningfor, size, or status",
Value: "created",
},
+ cli.BoolFlag{
+ Name: "sync",
+ Usage: "Sync container state with OCI runtime",
+ },
}
psDescription = "Prints out information about the containers"
psCommand = cli.Command{
@@ -260,6 +264,7 @@ func psCmd(c *cli.Context) error {
Size: c.Bool("size"),
Namespace: c.Bool("namespace"),
Sort: c.String("sort"),
+ Sync: c.Bool("sync"),
}
filters := c.StringSlice("filter")
diff --git a/cmd/podman/pull.go b/cmd/podman/pull.go
index 8fb3971bd..47130805e 100644
--- a/cmd/podman/pull.go
+++ b/cmd/podman/pull.go
@@ -64,7 +64,6 @@ specified, the image with the 'latest' tag (if it exists) is pulled
// pullCmd gets the data from the command line and calls pullImage
// to copy an image from a registry to a local machine
func pullCmd(c *cli.Context) error {
- forceSecure := false
runtime, err := libpodruntime.GetRuntime(c)
if err != nil {
return errors.Wrapf(err, "could not get runtime")
@@ -104,12 +103,11 @@ func pullCmd(c *cli.Context) error {
}
dockerRegistryOptions := image2.DockerRegistryOptions{
- DockerRegistryCreds: registryCreds,
- DockerCertPath: c.String("cert-dir"),
- DockerInsecureSkipTLSVerify: !c.BoolT("tls-verify"),
+ DockerRegistryCreds: registryCreds,
+ DockerCertPath: c.String("cert-dir"),
}
if c.IsSet("tls-verify") {
- forceSecure = c.Bool("tls-verify")
+ dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify"))
}
// Possible for docker-archive to have multiple tags, so use LoadFromArchiveReference instead
@@ -125,7 +123,7 @@ func pullCmd(c *cli.Context) error {
imgID = newImage[0].ID()
} else {
authfile := getAuthFile(c.String("authfile"))
- newImage, err := runtime.ImageRuntime().New(getContext(), image, c.String("signature-policy"), authfile, writer, &dockerRegistryOptions, image2.SigningOptions{}, true, forceSecure)
+ newImage, err := runtime.ImageRuntime().New(getContext(), image, c.String("signature-policy"), authfile, writer, &dockerRegistryOptions, image2.SigningOptions{}, true)
if err != nil {
return errors.Wrapf(err, "error pulling image %q", image)
}
diff --git a/cmd/podman/push.go b/cmd/podman/push.go
index 331f92cd2..82589f3f1 100644
--- a/cmd/podman/push.go
+++ b/cmd/podman/push.go
@@ -81,7 +81,6 @@ func pushCmd(c *cli.Context) error {
var (
registryCreds *types.DockerAuthConfig
destName string
- forceSecure bool
)
args := c.Args()
@@ -108,7 +107,6 @@ func pushCmd(c *cli.Context) error {
}
certPath := c.String("cert-dir")
- skipVerify := !c.BoolT("tls-verify")
removeSignatures := c.Bool("remove-signatures")
signBy := c.String("sign-by")
@@ -145,14 +143,12 @@ func pushCmd(c *cli.Context) error {
}
}
- if c.IsSet("tls-verify") {
- forceSecure = c.Bool("tls-verify")
- }
-
dockerRegistryOptions := image.DockerRegistryOptions{
- DockerRegistryCreds: registryCreds,
- DockerCertPath: certPath,
- DockerInsecureSkipTLSVerify: skipVerify,
+ DockerRegistryCreds: registryCreds,
+ DockerCertPath: certPath,
+ }
+ if c.IsSet("tls-verify") {
+ dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify"))
}
so := image.SigningOptions{
@@ -167,5 +163,5 @@ func pushCmd(c *cli.Context) error {
authfile := getAuthFile(c.String("authfile"))
- return newImage.PushImageToHeuristicDestination(getContext(), destName, manifestType, authfile, c.String("signature-policy"), writer, c.Bool("compress"), so, &dockerRegistryOptions, forceSecure, nil)
+ return newImage.PushImageToHeuristicDestination(getContext(), destName, manifestType, authfile, c.String("signature-policy"), writer, c.Bool("compress"), so, &dockerRegistryOptions, nil)
}
diff --git a/cmd/podman/rmi.go b/cmd/podman/rmi.go
index c0a0d69df..0f4f8765b 100644
--- a/cmd/podman/rmi.go
+++ b/cmd/podman/rmi.go
@@ -91,8 +91,23 @@ func rmiCmd(c *cli.Context) error {
if err != nil {
return errors.Wrapf(err, "unable to query local images")
}
- for _, i := range imagesToDelete {
- removeImage(i)
+ lastNumberofImages := 0
+ for len(imagesToDelete) > 0 {
+ if lastNumberofImages == len(imagesToDelete) {
+ return errors.New("unable to delete all images; re-run the rmi command again.")
+ }
+ for _, i := range imagesToDelete {
+ isParent, err := i.IsParent()
+ if err != nil {
+ return err
+ }
+ if isParent {
+ continue
+ }
+ removeImage(i)
+ }
+ lastNumberofImages = len(imagesToDelete)
+ imagesToDelete, err = runtime.ImageRuntime().GetImages()
}
} else {
// Create image.image objects for deletion from user input.
diff --git a/cmd/podman/run.go b/cmd/podman/run.go
index af6ced45d..a4b5c918e 100644
--- a/cmd/podman/run.go
+++ b/cmd/podman/run.go
@@ -44,7 +44,7 @@ func runCmd(c *cli.Context) error {
rootless.SetSkipStorageSetup(true)
}
- runtime, err := libpodruntime.GetContainerRuntime(c)
+ runtime, err := libpodruntime.GetRuntime(c)
if err != nil {
return errors.Wrapf(err, "error creating libpod runtime")
}
diff --git a/cmd/podman/runlabel.go b/cmd/podman/runlabel.go
index e1dee1fb2..48a296260 100644
--- a/cmd/podman/runlabel.go
+++ b/cmd/podman/runlabel.go
@@ -10,7 +10,6 @@ import (
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod/image"
- "github.com/containers/libpod/pkg/util"
"github.com/containers/libpod/utils"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -94,7 +93,7 @@ func runlabelCmd(c *cli.Context) error {
imageName string
stdErr, stdOut io.Writer
stdIn io.Reader
- newImage *image.Image
+ extraArgs []string
)
// Evil images could trick into recursively executing the runlabel
@@ -124,6 +123,9 @@ func runlabelCmd(c *cli.Context) error {
return errors.Errorf("the display and quiet flags cannot be used together.")
}
+ if len(args) > 2 {
+ extraArgs = args[2:]
+ }
pull := c.Bool("pull")
label := args[0]
@@ -151,75 +153,26 @@ func runlabelCmd(c *cli.Context) error {
stdIn = nil
}
- if pull {
- var registryCreds *types.DockerAuthConfig
- if c.IsSet("creds") {
- creds, err := util.ParseRegistryCreds(c.String("creds"))
- if err != nil {
- return err
- }
- registryCreds = creds
- }
- dockerRegistryOptions := image.DockerRegistryOptions{
- DockerRegistryCreds: registryCreds,
- DockerCertPath: c.String("cert-dir"),
- DockerInsecureSkipTLSVerify: !c.BoolT("tls-verify"),
- }
- authfile := getAuthFile(c.String("authfile"))
-
- newImage, err = runtime.ImageRuntime().New(ctx, runlabelImage, c.String("signature-policy"), authfile, stdOut, &dockerRegistryOptions, image.SigningOptions{}, false, false)
- } else {
- newImage, err = runtime.ImageRuntime().NewFromLocal(runlabelImage)
+ dockerRegistryOptions := image.DockerRegistryOptions{
+ DockerCertPath: c.String("cert-dir"),
}
- if err != nil {
- return errors.Wrapf(err, "unable to find image")
+ if c.IsSet("tls-verify") {
+ dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify"))
}
- if len(newImage.Names()) < 1 {
- imageName = newImage.ID()
- } else {
- imageName = newImage.Names()[0]
- }
-
- runLabel, err := newImage.GetLabel(ctx, label)
+ authfile := getAuthFile(c.String("authfile"))
+ runLabel, imageName, err := shared.GetRunlabel(label, runlabelImage, ctx, runtime, pull, c.String("creds"), dockerRegistryOptions, authfile, c.String("signature-policy"), stdOut)
if err != nil {
return err
}
-
- // If no label to execute, we return
if runLabel == "" {
return nil
}
- // The user provided extra arguments that need to be tacked onto the label's command
- if len(args) > 2 {
- runLabel = fmt.Sprintf("%s %s", runLabel, strings.Join(args[2:], " "))
- }
-
- cmd, err := shared.GenerateCommand(runLabel, imageName, c.String("name"))
+ cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, c.String("name"), opts, extraArgs)
if err != nil {
- return errors.Wrapf(err, "unable to generate command")
- }
- env := shared.GenerateRunEnvironment(c.String("name"), imageName, opts)
- env = append(env, "PODMAN_RUNLABEL_NESTED=1")
-
- envmap := envSliceToMap(env)
-
- envmapper := func(k string) string {
- switch k {
- case "OPT1":
- return envmap["OPT1"]
- case "OPT2":
- return envmap["OPT2"]
- case "OPT3":
- return envmap["OPT3"]
- }
- return ""
+ return err
}
-
- newS := os.Expand(strings.Join(cmd, " "), envmapper)
- cmd = strings.Split(newS, " ")
-
if !c.Bool("quiet") {
fmt.Printf("Command: %s\n", strings.Join(cmd, " "))
if c.Bool("display") {
@@ -228,12 +181,3 @@ func runlabelCmd(c *cli.Context) error {
}
return utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...)
}
-
-func envSliceToMap(env []string) map[string]string {
- m := make(map[string]string)
- for _, i := range env {
- split := strings.Split(i, "=")
- m[split[0]] = strings.Join(split[1:], " ")
- }
- return m
-}
diff --git a/cmd/podman/save.go b/cmd/podman/save.go
index 7edc42e0d..139f3918a 100644
--- a/cmd/podman/save.go
+++ b/cmd/podman/save.go
@@ -146,7 +146,7 @@ func saveCmd(c *cli.Context) error {
return err
}
}
- if err := newImage.PushImageToReference(getContext(), destRef, manifestType, "", "", writer, c.Bool("compress"), libpodImage.SigningOptions{}, &libpodImage.DockerRegistryOptions{}, false, additionaltags); err != nil {
+ if err := newImage.PushImageToReference(getContext(), destRef, manifestType, "", "", writer, c.Bool("compress"), libpodImage.SigningOptions{}, &libpodImage.DockerRegistryOptions{}, additionaltags); err != nil {
if err2 := os.Remove(output); err2 != nil {
logrus.Errorf("error deleting %q: %v", output, err)
}
diff --git a/cmd/podman/search.go b/cmd/podman/search.go
index fa11dad32..442ebb57f 100644
--- a/cmd/podman/search.go
+++ b/cmd/podman/search.go
@@ -7,6 +7,7 @@ import (
"strings"
"github.com/containers/image/docker"
+ "github.com/containers/image/types"
"github.com/containers/libpod/cmd/podman/formats"
"github.com/containers/libpod/libpod/common"
sysreg "github.com/containers/libpod/pkg/registries"
@@ -72,11 +73,12 @@ type searchParams struct {
}
type searchOpts struct {
- filter []string
- limit int
- noTrunc bool
- format string
- authfile string
+ filter []string
+ limit int
+ noTrunc bool
+ format string
+ authfile string
+ insecureSkipTLSVerify types.OptionalBool
}
type searchFilterParams struct {
@@ -116,7 +118,10 @@ func searchCmd(c *cli.Context) error {
filter: c.StringSlice("filter"),
authfile: getAuthFile(c.String("authfile")),
}
- regAndSkipTLS, err := getRegistriesAndSkipTLS(c, registry)
+ if c.IsSet("tls-verify") {
+ opts.insecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify"))
+ }
+ registries, err := getRegistries(registry)
if err != nil {
return err
}
@@ -126,7 +131,7 @@ func searchCmd(c *cli.Context) error {
return err
}
- return generateSearchOutput(term, regAndSkipTLS, opts, *filter)
+ return generateSearchOutput(term, registries, opts, *filter)
}
func genSearchFormat(format string) string {
@@ -157,16 +162,8 @@ func (s *searchParams) headerMap() map[string]string {
return values
}
-// A function for finding which registries can skip TLS
-func getRegistriesAndSkipTLS(c *cli.Context, registry string) (map[string]bool, error) {
- // Variables for setting up Registry and TLSVerify
- tlsVerify := c.BoolT("tls-verify")
- forceSecure := false
-
- if c.IsSet("tls-verify") {
- forceSecure = c.BoolT("tls-verify")
- }
-
+// getRegistries returns the list of registries to search, depending on an optional registry specification
+func getRegistries(registry string) ([]string, error) {
var registries []string
if registry != "" {
registries = append(registries, registry)
@@ -177,35 +174,10 @@ func getRegistriesAndSkipTLS(c *cli.Context, registry string) (map[string]bool,
return nil, errors.Wrapf(err, "error getting registries to search")
}
}
- regAndSkipTLS := make(map[string]bool)
- // If tls-verify is set to false, allow insecure always.
- if !tlsVerify {
- for _, reg := range registries {
- regAndSkipTLS[reg] = true
- }
- } else {
- // initially set all registries to verify with TLS
- for _, reg := range registries {
- regAndSkipTLS[reg] = false
- }
- // if the user didn't allow nor disallow insecure registries, check to see if the registry is insecure
- if !forceSecure {
- insecureRegistries, err := sysreg.GetInsecureRegistries()
- if err != nil {
- return nil, errors.Wrapf(err, "error getting insecure registries to search")
- }
- for _, reg := range insecureRegistries {
- // if there are any insecure registries in registries, allow for HTTP
- if _, ok := regAndSkipTLS[reg]; ok {
- regAndSkipTLS[reg] = true
- }
- }
- }
- }
- return regAndSkipTLS, nil
+ return registries, nil
}
-func getSearchOutput(term string, regAndSkipTLS map[string]bool, opts searchOpts, filter searchFilterParams) ([]searchParams, error) {
+func getSearchOutput(term string, registries []string, opts searchOpts, filter searchFilterParams) ([]searchParams, error) {
// Max number of queries by default is 25
limit := maxQueries
if opts.limit != 0 {
@@ -213,10 +185,10 @@ func getSearchOutput(term string, regAndSkipTLS map[string]bool, opts searchOpts
}
sc := common.GetSystemContext("", opts.authfile, false)
+ sc.DockerInsecureSkipTLSVerify = opts.insecureSkipTLSVerify
+ sc.SystemRegistriesConfPath = sysreg.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.
var paramsArr []searchParams
- for reg, skipTLS := range regAndSkipTLS {
- // set the SkipTLSVerify bool depending on the registry being searched through
- sc.DockerInsecureSkipTLSVerify = skipTLS
+ for _, reg := range registries {
results, err := docker.SearchRegistry(context.TODO(), sc, reg, term, limit)
if err != nil {
logrus.Errorf("error searching registry %q: %v", reg, err)
@@ -276,8 +248,8 @@ func getSearchOutput(term string, regAndSkipTLS map[string]bool, opts searchOpts
return paramsArr, nil
}
-func generateSearchOutput(term string, regAndSkipTLS map[string]bool, opts searchOpts, filter searchFilterParams) error {
- searchOutput, err := getSearchOutput(term, regAndSkipTLS, opts, filter)
+func generateSearchOutput(term string, registries []string, opts searchOpts, filter searchFilterParams) error {
+ searchOutput, err := getSearchOutput(term, registries, opts, filter)
if err != nil {
return err
}
diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go
index 4404268d4..6236d19b4 100644
--- a/cmd/podman/shared/container.go
+++ b/cmd/podman/shared/container.go
@@ -1,10 +1,10 @@
package shared
import (
+ "context"
"encoding/json"
"fmt"
- "github.com/cri-o/ocicni/pkg/ocicni"
- "github.com/docker/go-units"
+ "io"
"os"
"path/filepath"
"regexp"
@@ -13,9 +13,14 @@ import (
"sync"
"time"
+ "github.com/containers/image/types"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/inspect"
cc "github.com/containers/libpod/pkg/spec"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/cri-o/ocicni/pkg/ocicni"
+ "github.com/docker/go-units"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -40,6 +45,7 @@ type PsOptions struct {
Sort string
Label string
Namespace bool
+ Sync bool
}
// BatchContainerStruct is the return obkect from BatchContainer and contains
@@ -121,6 +127,12 @@ func NewBatchContainer(ctr *libpod.Container, opts PsOptions) (PsContainerOutput
pso PsContainerOutput
)
batchErr := ctr.Batch(func(c *libpod.Container) error {
+ if opts.Sync {
+ if err := c.Sync(); err != nil {
+ return err
+ }
+ }
+
conState, err = c.State()
if err != nil {
return errors.Wrapf(err, "unable to obtain container state")
@@ -589,3 +601,79 @@ func portsToString(ports []ocicni.PortMapping) string {
}
return strings.Join(portDisplay, ", ")
}
+
+// GetRunlabel is a helper function for runlabel; it gets the image if needed and begins the
+// contruction of the runlabel output and environment variables
+func GetRunlabel(label string, runlabelImage string, ctx context.Context, runtime *libpod.Runtime, pull bool, inputCreds string, dockerRegistryOptions image.DockerRegistryOptions, authfile string, signaturePolicyPath string, output io.Writer) (string, string, error) {
+ var (
+ newImage *image.Image
+ err error
+ imageName string
+ )
+ if pull {
+ var registryCreds *types.DockerAuthConfig
+ if inputCreds != "" {
+ creds, err := util.ParseRegistryCreds(inputCreds)
+ if err != nil {
+ return "", "", err
+ }
+ registryCreds = creds
+ }
+ dockerRegistryOptions.DockerRegistryCreds = registryCreds
+ newImage, err = runtime.ImageRuntime().New(ctx, runlabelImage, signaturePolicyPath, authfile, output, &dockerRegistryOptions, image.SigningOptions{}, false)
+ } else {
+ newImage, err = runtime.ImageRuntime().NewFromLocal(runlabelImage)
+ }
+ if err != nil {
+ return "", "", errors.Wrapf(err, "unable to find image")
+ }
+
+ if len(newImage.Names()) < 1 {
+ imageName = newImage.ID()
+ } else {
+ imageName = newImage.Names()[0]
+ }
+
+ runLabel, err := newImage.GetLabel(ctx, label)
+ return runLabel, imageName, err
+}
+
+// GenerateRunlabelCommand generates the command that will eventually be execucted by podman
+func GenerateRunlabelCommand(runLabel, imageName, name string, opts map[string]string, extraArgs []string) ([]string, []string, error) {
+ // The user provided extra arguments that need to be tacked onto the label's command
+ if len(extraArgs) > 0 {
+ runLabel = fmt.Sprintf("%s %s", runLabel, strings.Join(extraArgs, " "))
+ }
+ cmd, err := GenerateCommand(runLabel, imageName, name)
+ if err != nil {
+ return nil, nil, errors.Wrapf(err, "unable to generate command")
+ }
+ env := GenerateRunEnvironment(name, imageName, opts)
+ env = append(env, "PODMAN_RUNLABEL_NESTED=1")
+
+ envmap := envSliceToMap(env)
+
+ envmapper := func(k string) string {
+ switch k {
+ case "OPT1":
+ return envmap["OPT1"]
+ case "OPT2":
+ return envmap["OPT2"]
+ case "OPT3":
+ return envmap["OPT3"]
+ }
+ return ""
+ }
+ newS := os.Expand(strings.Join(cmd, " "), envmapper)
+ cmd = strings.Split(newS, " ")
+ return cmd, env, nil
+}
+
+func envSliceToMap(env []string) map[string]string {
+ m := make(map[string]string)
+ for _, i := range env {
+ split := strings.Split(i, "=")
+ m[split[0]] = strings.Join(split[1:], " ")
+ }
+ return m
+}
diff --git a/cmd/podman/shared/prune.go b/cmd/podman/shared/prune.go
new file mode 100644
index 000000000..90cfe4475
--- /dev/null
+++ b/cmd/podman/shared/prune.go
@@ -0,0 +1,24 @@
+package shared
+
+import (
+ "fmt"
+ "github.com/pkg/errors"
+
+ "github.com/containers/libpod/libpod/image"
+)
+
+// Prune removes all unnamed and unused images from the local store
+func Prune(ir *image.Runtime) error {
+ pruneImages, err := ir.GetPruneImages()
+ if err != nil {
+ return err
+ }
+
+ for _, i := range pruneImages {
+ if err := i.Remove(true); err != nil {
+ return errors.Wrapf(err, "failed to remove %s", i.ID())
+ }
+ fmt.Println(i.ID())
+ }
+ return nil
+}
diff --git a/cmd/podman/utils.go b/cmd/podman/utils.go
index 5735156c2..a59535b43 100644
--- a/cmd/podman/utils.go
+++ b/cmd/podman/utils.go
@@ -3,6 +3,9 @@ package main
import (
"context"
"fmt"
+ "os"
+ gosignal "os/signal"
+
"github.com/containers/libpod/libpod"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/term"
@@ -11,8 +14,6 @@ import (
"github.com/urfave/cli"
"golang.org/x/crypto/ssh/terminal"
"k8s.io/client-go/tools/remotecommand"
- "os"
- gosignal "os/signal"
)
type RawTtyFormatter struct {
@@ -208,6 +209,35 @@ func getPodsFromContext(c *cli.Context, r *libpod.Runtime) ([]*libpod.Pod, error
return pods, lastError
}
+func getVolumesFromContext(c *cli.Context, r *libpod.Runtime) ([]*libpod.Volume, error) {
+ args := c.Args()
+ var (
+ vols []*libpod.Volume
+ lastError error
+ err error
+ )
+
+ if c.Bool("all") {
+ vols, err = r.Volumes()
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to get all volumes")
+ }
+ }
+
+ for _, i := range args {
+ vol, err := r.GetVolume(i)
+ if err != nil {
+ if lastError != nil {
+ logrus.Errorf("%q", lastError)
+ }
+ lastError = errors.Wrapf(err, "unable to find volume %s", i)
+ continue
+ }
+ vols = append(vols, vol)
+ }
+ return vols, lastError
+}
+
//printParallelOutput takes the map of parallel worker results and outputs them
// to stdout
func printParallelOutput(m map[string]error, errCount int) error {
diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink
index 4a4a1854c..486f4e60c 100644
--- a/cmd/podman/varlink/io.podman.varlink
+++ b/cmd/podman/varlink/io.podman.varlink
@@ -371,6 +371,22 @@ type PodContainerErrorData (
reason: string
)
+# Runlabel describes the required input for container runlabel
+type Runlabel(
+ image: string,
+ authfile: string,
+ certDir: string,
+ creds: string,
+ display: bool,
+ name: string,
+ pull: bool,
+ signaturePolicyPath: string,
+ tlsVerify: bool,
+ label: string,
+ extraArgs: []string,
+ opts: [string]string
+)
+
# Ping provides a response for developers to ensure their varlink setup is working.
# #### Example
# ~~~
@@ -594,7 +610,8 @@ method InspectImage(name: string) -> (image: string)
method HistoryImage(name: string) -> (history: []ImageHistory)
# PushImage takes three input arguments: the name or ID of an image, the fully-qualified destination name of the image,
-# and a boolean as to whether tls-verify should be used. It will return an [ImageNotFound](#ImageNotFound) error if
+# and a boolean as to whether tls-verify should be used (with false disabling TLS, not affecting the default behavior).
+# It will return an [ImageNotFound](#ImageNotFound) error if
# the image cannot be found in local storage; otherwise the ID of the image will be returned on success.
method PushImage(name: string, tag: string, tlsverify: bool) -> (image: string)
@@ -804,6 +821,42 @@ method TopPod() -> (notimplemented: NotImplemented)
# ~~~
method GetPodStats(name: string) -> (pod: string, containers: []ContainerStats)
+# ImageExists talks a full or partial image ID or name and returns an int as to whether
+# the image exists in local storage. An int result of 0 means the image does exist in
+# local storage; whereas 1 indicates the image does not exists in local storage.
+method ImageExists(name: string) -> (exists: int)
+
+# ContainerExists takes a full or partial container ID or name and returns an int as to
+# whether the container exists in local storage. A result of 0 means the container does
+# exists; whereas a result of 1 means it could not be found.
+method ContainerExists(name: string) -> (exists: int)
+
+# ContainerCheckPoint performs a checkpopint on a container by its name or full/partial container
+# ID. On successful checkpoint, the id of the checkpointed container is returned.
+method ContainerCheckpoint(name: string, keep: bool, leaveRunning: bool, tcpEstablished: bool) -> (id: string)
+
+# ContainerRestore restores a container that has been checkpointed. The container to be restored can
+# be identified by its name or full/partial container ID. A successful restore will result in the return
+# of the container's ID.
+method ContainerRestore(name: string, keep: bool, tcpEstablished: bool) -> (id: string)
+
+# ContainerRunlabel runs executes a command as described by a given container image label.
+method ContainerRunlabel(runlabel: Runlabel) -> ()
+
+# ListContainerMounts gathers all the mounted container mount points and returns them as an array
+# of strings
+method ListContainerMounts() -> (mounts: []string)
+
+# MountContainer mounts a container by name or full/partial ID. Upon a successful mount, the destination
+# mount is returned as a string.
+method MountContainer(name: string) -> (path: string)
+
+# UnmountContainer umounts a container by its name or full/partial container ID.
+method UnmountContainer(name: string, force: bool) -> ()
+
+# This function is not implemented yet.
+method ListContainerPorts(name: string) -> (notimplemented: NotImplemented)
+
# ImageNotFound means the image could not be found by the provided name or ID in local storage.
error ImageNotFound (name: string)
diff --git a/cmd/podman/volume.go b/cmd/podman/volume.go
new file mode 100644
index 000000000..913592e74
--- /dev/null
+++ b/cmd/podman/volume.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+ "github.com/urfave/cli"
+)
+
+var (
+ volumeDescription = `Manage volumes.
+
+Volumes are created in and can be shared between containers.`
+
+ volumeSubCommands = []cli.Command{
+ volumeCreateCommand,
+ volumeLsCommand,
+ volumeRmCommand,
+ volumeInspectCommand,
+ volumePruneCommand,
+ }
+ volumeCommand = cli.Command{
+ Name: "volume",
+ Usage: "Manage volumes",
+ Description: volumeDescription,
+ UseShortOptionHandling: true,
+ Subcommands: volumeSubCommands,
+ }
+)
diff --git a/cmd/podman/volume_create.go b/cmd/podman/volume_create.go
new file mode 100644
index 000000000..0b5f8d1e3
--- /dev/null
+++ b/cmd/podman/volume_create.go
@@ -0,0 +1,97 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/libpod"
+ "github.com/pkg/errors"
+ "github.com/urfave/cli"
+)
+
+var volumeCreateDescription = `
+podman volume create
+
+Creates a new volume. If using the default driver, "local", the volume will
+be created at.`
+
+var volumeCreateFlags = []cli.Flag{
+ cli.StringFlag{
+ Name: "driver",
+ Usage: "Specify volume driver name (default local)",
+ },
+ cli.StringSliceFlag{
+ Name: "label, l",
+ Usage: "Set metadata for a volume (default [])",
+ },
+ cli.StringSliceFlag{
+ Name: "opt, o",
+ Usage: "Set driver specific options (default [])",
+ },
+}
+
+var volumeCreateCommand = cli.Command{
+ Name: "create",
+ Usage: "Create a new volume",
+ Description: volumeCreateDescription,
+ Flags: volumeCreateFlags,
+ Action: volumeCreateCmd,
+ SkipArgReorder: true,
+ ArgsUsage: "[VOLUME-NAME]",
+ UseShortOptionHandling: true,
+}
+
+func volumeCreateCmd(c *cli.Context) error {
+ var (
+ options []libpod.VolumeCreateOption
+ err error
+ volName string
+ )
+
+ if err = validateFlags(c, volumeCreateFlags); err != nil {
+ return err
+ }
+
+ runtime, err := libpodruntime.GetRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "error creating libpod runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ if len(c.Args()) > 1 {
+ return errors.Errorf("too many arguments, create takes at most 1 argument")
+ }
+
+ if len(c.Args()) > 0 {
+ volName = c.Args()[0]
+ options = append(options, libpod.WithVolumeName(volName))
+ }
+
+ if c.IsSet("driver") {
+ options = append(options, libpod.WithVolumeDriver(c.String("driver")))
+ }
+
+ labels, err := getAllLabels([]string{}, c.StringSlice("label"))
+ if err != nil {
+ return errors.Wrapf(err, "unable to process labels")
+ }
+ if len(labels) != 0 {
+ options = append(options, libpod.WithVolumeLabels(labels))
+ }
+
+ opts, err := getAllLabels([]string{}, c.StringSlice("opt"))
+ if err != nil {
+ return errors.Wrapf(err, "unable to process options")
+ }
+ if len(options) != 0 {
+ options = append(options, libpod.WithVolumeOptions(opts))
+ }
+
+ vol, err := runtime.NewVolume(getContext(), options...)
+ if err != nil {
+ return err
+ }
+ fmt.Printf("%s\n", vol.Name())
+
+ return nil
+}
diff --git a/cmd/podman/volume_inspect.go b/cmd/podman/volume_inspect.go
new file mode 100644
index 000000000..152f1d098
--- /dev/null
+++ b/cmd/podman/volume_inspect.go
@@ -0,0 +1,63 @@
+package main
+
+import (
+ "github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/urfave/cli"
+)
+
+var volumeInspectDescription = `
+podman volume inspect
+
+Display detailed information on one or more volumes. Can change the format
+from JSON to a Go template.
+`
+
+var volumeInspectFlags = []cli.Flag{
+ cli.BoolFlag{
+ Name: "all, a",
+ Usage: "Inspect all volumes",
+ },
+ cli.StringFlag{
+ Name: "format, f",
+ Usage: "Format volume output using Go template",
+ Value: "json",
+ },
+}
+
+var volumeInspectCommand = cli.Command{
+ Name: "inspect",
+ Usage: "Display detailed information on one or more volumes",
+ Description: volumeInspectDescription,
+ Flags: volumeInspectFlags,
+ Action: volumeInspectCmd,
+ SkipArgReorder: true,
+ ArgsUsage: "[VOLUME-NAME ...]",
+ UseShortOptionHandling: true,
+}
+
+func volumeInspectCmd(c *cli.Context) error {
+ var err error
+
+ if err = validateFlags(c, volumeInspectFlags); err != nil {
+ return err
+ }
+
+ runtime, err := libpodruntime.GetRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "error creating libpod runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ opts := volumeLsOptions{
+ Format: c.String("format"),
+ }
+
+ vols, lastError := getVolumesFromContext(c, runtime)
+ if lastError != nil {
+ logrus.Errorf("%q", lastError)
+ }
+
+ return generateVolLsOutput(vols, opts, runtime)
+}
diff --git a/cmd/podman/volume_ls.go b/cmd/podman/volume_ls.go
new file mode 100644
index 000000000..0f94549ee
--- /dev/null
+++ b/cmd/podman/volume_ls.go
@@ -0,0 +1,308 @@
+package main
+
+import (
+ "reflect"
+ "strings"
+
+ "github.com/containers/libpod/cmd/podman/formats"
+ "github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/libpod"
+ "github.com/pkg/errors"
+ "github.com/urfave/cli"
+)
+
+// volumeOptions is the "ls" command options
+type volumeLsOptions struct {
+ Format string
+ Quiet bool
+}
+
+// volumeLsTemplateParams is the template parameters to list the volumes
+type volumeLsTemplateParams struct {
+ Name string
+ Labels string
+ MountPoint string
+ Driver string
+ Options string
+ Scope string
+}
+
+// volumeLsJSONParams is the JSON parameters to list the volumes
+type volumeLsJSONParams struct {
+ Name string `json:"name"`
+ Labels map[string]string `json:"labels"`
+ MountPoint string `json:"mountPoint"`
+ Driver string `json:"driver"`
+ Options map[string]string `json:"options"`
+ Scope string `json:"scope"`
+}
+
+var volumeLsDescription = `
+podman volume ls
+
+List all available volumes. The output of the volumes can be filtered
+and the output format can be changed to JSON or a user specified Go template.
+`
+
+var volumeLsFlags = []cli.Flag{
+ cli.StringFlag{
+ Name: "filter, f",
+ Usage: "Filter volume output",
+ },
+ cli.StringFlag{
+ Name: "format",
+ Usage: "Format volume output using Go template",
+ Value: "table {{.Driver}}\t{{.Name}}",
+ },
+ cli.BoolFlag{
+ Name: "quiet, q",
+ Usage: "Print volume output in quiet mode",
+ },
+}
+
+var volumeLsCommand = cli.Command{
+ Name: "ls",
+ Aliases: []string{"list"},
+ Usage: "List volumes",
+ Description: volumeLsDescription,
+ Flags: volumeLsFlags,
+ Action: volumeLsCmd,
+ SkipArgReorder: true,
+ UseShortOptionHandling: true,
+}
+
+func volumeLsCmd(c *cli.Context) error {
+ if err := validateFlags(c, volumeLsFlags); err != nil {
+ return err
+ }
+
+ runtime, err := libpodruntime.GetRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "error creating libpod runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ if len(c.Args()) > 0 {
+ return errors.Errorf("too many arguments, ls takes no arguments")
+ }
+
+ opts := volumeLsOptions{
+ Quiet: c.Bool("quiet"),
+ }
+ opts.Format = genVolLsFormat(c)
+
+ // Get the filter functions based on any filters set
+ var filterFuncs []libpod.VolumeFilter
+ if c.String("filter") != "" {
+ filters := strings.Split(c.String("filter"), ",")
+ for _, f := range filters {
+ filterSplit := strings.Split(f, "=")
+ if len(filterSplit) < 2 {
+ return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
+ }
+ generatedFunc, err := generateVolumeFilterFuncs(filterSplit[0], filterSplit[1], runtime)
+ if err != nil {
+ return errors.Wrapf(err, "invalid filter")
+ }
+ filterFuncs = append(filterFuncs, generatedFunc)
+ }
+ }
+
+ volumes, err := runtime.GetAllVolumes()
+ if err != nil {
+ return err
+ }
+
+ // Get the volumes that match the filter
+ volsFiltered := make([]*libpod.Volume, 0, len(volumes))
+ for _, vol := range volumes {
+ include := true
+ for _, filter := range filterFuncs {
+ include = include && filter(vol)
+ }
+
+ if include {
+ volsFiltered = append(volsFiltered, vol)
+ }
+ }
+ return generateVolLsOutput(volsFiltered, opts, runtime)
+}
+
+// generate the template based on conditions given
+func genVolLsFormat(c *cli.Context) string {
+ var format string
+ if c.String("format") != "" {
+ // "\t" from the command line is not being recognized as a tab
+ // replacing the string "\t" to a tab character if the user passes in "\t"
+ format = strings.Replace(c.String("format"), `\t`, "\t", -1)
+ }
+ if c.Bool("quiet") {
+ format = "{{.Name}}"
+ }
+ return format
+}
+
+// Convert output to genericParams for printing
+func volLsToGeneric(templParams []volumeLsTemplateParams, JSONParams []volumeLsJSONParams) (genericParams []interface{}) {
+ if len(templParams) > 0 {
+ for _, v := range templParams {
+ genericParams = append(genericParams, interface{}(v))
+ }
+ return
+ }
+ for _, v := range JSONParams {
+ genericParams = append(genericParams, interface{}(v))
+ }
+ return
+}
+
+// generate the accurate header based on template given
+func (vol *volumeLsTemplateParams) volHeaderMap() map[string]string {
+ v := reflect.Indirect(reflect.ValueOf(vol))
+ values := make(map[string]string)
+
+ for i := 0; i < v.NumField(); i++ {
+ key := v.Type().Field(i).Name
+ value := key
+ if value == "Name" {
+ value = "Volume" + value
+ }
+ values[key] = strings.ToUpper(splitCamelCase(value))
+ }
+ return values
+}
+
+// getVolTemplateOutput returns all the volumes in the volumeLsTemplateParams format
+func getVolTemplateOutput(lsParams []volumeLsJSONParams, opts volumeLsOptions) ([]volumeLsTemplateParams, error) {
+ var lsOutput []volumeLsTemplateParams
+
+ for _, lsParam := range lsParams {
+ var (
+ labels string
+ options string
+ )
+
+ for k, v := range lsParam.Labels {
+ label := k
+ if v != "" {
+ label += "=" + v
+ }
+ labels += label
+ }
+ for k, v := range lsParam.Options {
+ option := k
+ if v != "" {
+ option += "=" + v
+ }
+ options += option
+ }
+ params := volumeLsTemplateParams{
+ Name: lsParam.Name,
+ Driver: lsParam.Driver,
+ MountPoint: lsParam.MountPoint,
+ Scope: lsParam.Scope,
+ Labels: labels,
+ Options: options,
+ }
+
+ lsOutput = append(lsOutput, params)
+ }
+ return lsOutput, nil
+}
+
+// getVolJSONParams returns the volumes in JSON format
+func getVolJSONParams(volumes []*libpod.Volume, opts volumeLsOptions, runtime *libpod.Runtime) ([]volumeLsJSONParams, error) {
+ var lsOutput []volumeLsJSONParams
+
+ for _, volume := range volumes {
+ params := volumeLsJSONParams{
+ Name: volume.Name(),
+ Labels: volume.Labels(),
+ MountPoint: volume.MountPoint(),
+ Driver: volume.Driver(),
+ Options: volume.Options(),
+ Scope: volume.Scope(),
+ }
+
+ lsOutput = append(lsOutput, params)
+ }
+ return lsOutput, nil
+}
+
+// generateVolLsOutput generates the output based on the format, JSON or Go Template, and prints it out
+func generateVolLsOutput(volumes []*libpod.Volume, opts volumeLsOptions, runtime *libpod.Runtime) error {
+ if len(volumes) == 0 && opts.Format != formats.JSONString {
+ return nil
+ }
+ lsOutput, err := getVolJSONParams(volumes, opts, runtime)
+ if err != nil {
+ return err
+ }
+ var out formats.Writer
+
+ switch opts.Format {
+ case formats.JSONString:
+ if err != nil {
+ return errors.Wrapf(err, "unable to create JSON for volume output")
+ }
+ out = formats.JSONStructArray{Output: volLsToGeneric([]volumeLsTemplateParams{}, lsOutput)}
+ default:
+ lsOutput, err := getVolTemplateOutput(lsOutput, opts)
+ if err != nil {
+ return errors.Wrapf(err, "unable to create volume output")
+ }
+ out = formats.StdoutTemplateArray{Output: volLsToGeneric(lsOutput, []volumeLsJSONParams{}), Template: opts.Format, Fields: lsOutput[0].volHeaderMap()}
+ }
+ return formats.Writer(out).Out()
+}
+
+// generateVolumeFilterFuncs returns the true if the volume matches the filter set, otherwise it returns false.
+func generateVolumeFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) (func(volume *libpod.Volume) bool, error) {
+ switch filter {
+ case "name":
+ return func(v *libpod.Volume) bool {
+ return strings.Contains(v.Name(), filterValue)
+ }, nil
+ case "driver":
+ return func(v *libpod.Volume) bool {
+ return v.Driver() == filterValue
+ }, nil
+ case "scope":
+ return func(v *libpod.Volume) bool {
+ return v.Scope() == filterValue
+ }, nil
+ case "label":
+ filterArray := strings.SplitN(filterValue, "=", 2)
+ filterKey := filterArray[0]
+ if len(filterArray) > 1 {
+ filterValue = filterArray[1]
+ } else {
+ filterValue = ""
+ }
+ return func(v *libpod.Volume) bool {
+ for labelKey, labelValue := range v.Labels() {
+ if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) {
+ return true
+ }
+ }
+ return false
+ }, nil
+ case "opt":
+ filterArray := strings.SplitN(filterValue, "=", 2)
+ filterKey := filterArray[0]
+ if len(filterArray) > 1 {
+ filterValue = filterArray[1]
+ } else {
+ filterValue = ""
+ }
+ return func(v *libpod.Volume) bool {
+ for labelKey, labelValue := range v.Options() {
+ if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) {
+ return true
+ }
+ }
+ return false
+ }, nil
+ }
+ return nil, errors.Errorf("%s is an invalid filter", filter)
+}
diff --git a/cmd/podman/volume_prune.go b/cmd/podman/volume_prune.go
new file mode 100644
index 000000000..652c50f42
--- /dev/null
+++ b/cmd/podman/volume_prune.go
@@ -0,0 +1,86 @@
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/libpod"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/urfave/cli"
+)
+
+var volumePruneDescription = `
+podman volume prune
+
+Remove all unused volumes. Will prompt for confirmation if not
+using force.
+`
+
+var volumePruneFlags = []cli.Flag{
+ cli.BoolFlag{
+ Name: "force, f",
+ Usage: "Do not prompt for confirmation",
+ },
+}
+
+var volumePruneCommand = cli.Command{
+ Name: "prune",
+ Usage: "Remove all unused volumes",
+ Description: volumePruneDescription,
+ Flags: volumePruneFlags,
+ Action: volumePruneCmd,
+ SkipArgReorder: true,
+ UseShortOptionHandling: true,
+}
+
+func volumePruneCmd(c *cli.Context) error {
+ var lastError error
+
+ if err := validateFlags(c, volumePruneFlags); err != nil {
+ return err
+ }
+
+ runtime, err := libpodruntime.GetRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "error creating libpod runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ ctx := getContext()
+
+ // Prompt for confirmation if --force is not set
+ if !c.Bool("force") {
+ reader := bufio.NewReader(os.Stdin)
+ fmt.Println("WARNING! This will remove all volumes not used by at least one container.")
+ fmt.Print("Are you sure you want to continue? [y/N] ")
+ ans, err := reader.ReadString('\n')
+ if err != nil {
+ return errors.Wrapf(err, "error reading input")
+ }
+ if strings.ToLower(ans)[0] != 'y' {
+ return nil
+ }
+ }
+
+ volumes, err := runtime.GetAllVolumes()
+ if err != nil {
+ return err
+ }
+
+ for _, vol := range volumes {
+ err = runtime.RemoveVolume(ctx, vol, false, true)
+ if err == nil {
+ fmt.Println(vol.Name())
+ } else if err != libpod.ErrVolumeBeingUsed {
+ if lastError != nil {
+ logrus.Errorf("%q", lastError)
+ }
+ lastError = errors.Wrapf(err, "failed to remove volume %q", vol.Name())
+ }
+ }
+ return lastError
+}
diff --git a/cmd/podman/volume_rm.go b/cmd/podman/volume_rm.go
new file mode 100644
index 000000000..3fb623624
--- /dev/null
+++ b/cmd/podman/volume_rm.go
@@ -0,0 +1,71 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/urfave/cli"
+)
+
+var volumeRmDescription = `
+podman volume rm
+
+Remove one or more existing volumes. Will only remove volumes that are
+not being used by any containers. To remove the volumes anyways, use the
+--force flag.
+`
+
+var volumeRmFlags = []cli.Flag{
+ cli.BoolFlag{
+ Name: "all, a",
+ Usage: "Remove all volumes",
+ },
+ cli.BoolFlag{
+ Name: "force, f",
+ Usage: "Remove a volume by force, even if it is being used by a container",
+ },
+}
+
+var volumeRmCommand = cli.Command{
+ Name: "rm",
+ Aliases: []string{"remove"},
+ Usage: "Remove one or more volumes",
+ Description: volumeRmDescription,
+ Flags: volumeRmFlags,
+ Action: volumeRmCmd,
+ ArgsUsage: "[VOLUME-NAME ...]",
+ SkipArgReorder: true,
+ UseShortOptionHandling: true,
+}
+
+func volumeRmCmd(c *cli.Context) error {
+ var err error
+
+ if err = validateFlags(c, volumeRmFlags); err != nil {
+ return err
+ }
+
+ runtime, err := libpodruntime.GetRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "error creating libpod runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ ctx := getContext()
+
+ vols, lastError := getVolumesFromContext(c, runtime)
+ for _, vol := range vols {
+ err = runtime.RemoveVolume(ctx, vol, c.Bool("force"), false)
+ if err != nil {
+ if lastError != nil {
+ logrus.Errorf("%q", lastError)
+ }
+ lastError = errors.Wrapf(err, "failed to remove volume %q", vol.Name())
+ } else {
+ fmt.Println(vol.Name())
+ }
+ }
+ return lastError
+}