diff options
Diffstat (limited to 'cmd/podman')
28 files changed, 1128 insertions, 206 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 228438d75..6c6bcfb41 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -129,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 } 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/generate.go b/cmd/podman/generate.go new file mode 100644 index 000000000..765d0ee70 --- /dev/null +++ b/cmd/podman/generate.go @@ -0,0 +1,23 @@ +package main + +import ( + "github.com/urfave/cli" +) + +var ( + generateSubCommands = []cli.Command{ + containerKubeCommand, + } + + generateDescription = "generate structured data based for a containers and pods" + kubeCommand = cli.Command{ + Name: "generate", + Usage: "generated structured data", + Description: generateDescription, + ArgsUsage: "", + Subcommands: generateSubCommands, + UseShortOptionHandling: true, + OnUsageError: usageErrorHandler, + Hidden: true, + } +) diff --git a/cmd/podman/kube_generate.go b/cmd/podman/generate_kube.go index a18912668..de9f701b0 100644 --- a/cmd/podman/kube_generate.go +++ b/cmd/podman/generate_kube.go @@ -6,10 +6,11 @@ import ( "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" + podmanVersion "github.com/containers/libpod/version" "github.com/ghodss/yaml" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/urfave/cli" + "k8s.io/api/core/v1" ) var ( @@ -18,16 +19,15 @@ var ( Name: "service, s", Usage: "only generate YAML for kubernetes service object", }, - LatestFlag, } containerKubeDescription = "Generate Kubernetes Pod YAML" containerKubeCommand = cli.Command{ - Name: "generate", - Usage: "Generate Kubernetes pod YAML for a container", + Name: "kube", + Usage: "Generate Kubernetes pod YAML for a container or pod", Description: containerKubeDescription, Flags: sortFlags(containerKubeFlags), Action: generateKubeYAMLCmd, - ArgsUsage: "CONTAINER-NAME", + ArgsUsage: "CONTAINER|POD-NAME", UseShortOptionHandling: true, OnUsageError: usageErrorHandler, } @@ -36,9 +36,13 @@ var ( // generateKubeYAMLCmdgenerates or replays kube func generateKubeYAMLCmd(c *cli.Context) error { var ( - container *libpod.Container - err error - output []byte + podYAML *v1.Pod + container *libpod.Container + err error + output []byte + pod *libpod.Pod + mashalledBytes []byte + servicePorts []v1.ServicePort ) if rootless.IsRootless() { @@ -46,10 +50,7 @@ func generateKubeYAMLCmd(c *cli.Context) error { } args := c.Args() if len(args) > 1 || (len(args) < 1 && !c.Bool("latest")) { - return errors.Errorf("you must provide one container ID or name or --latest") - } - if c.Bool("service") { - return errors.Wrapf(libpod.ErrNotImplemented, "service generation") + return errors.Errorf("you must provide one container|pod ID or name or --latest") } runtime, err := libpodruntime.GetRuntime(c) @@ -59,33 +60,43 @@ func generateKubeYAMLCmd(c *cli.Context) error { defer runtime.Shutdown(false) // Get the container in question - if c.Bool("latest") { - container, err = runtime.GetLatestContainer() + container, err = runtime.LookupContainer(args[0]) + if err != nil { + pod, err = runtime.LookupPod(args[0]) + if err != nil { + return err + } + podYAML, servicePorts, err = pod.GenerateForKube() } else { - container, err = runtime.LookupContainer(args[0]) + if len(container.Dependencies()) > 0 { + return errors.Wrapf(libpod.ErrNotImplemented, "containers with dependencies") + } + podYAML, err = container.GenerateForKube() } if err != nil { return err } - if len(container.Dependencies()) > 0 { - return errors.Wrapf(libpod.ErrNotImplemented, "containers with dependencies") + if c.Bool("service") { + serviceYAML := libpod.GenerateKubeServiceFromV1Pod(podYAML, servicePorts) + mashalledBytes, err = yaml.Marshal(serviceYAML) + } else { + // Marshall the results + mashalledBytes, err = yaml.Marshal(podYAML) } - - podYAML, err := container.InspectForKube() if err != nil { return err } - developmentComment := []byte("# Generation of Kubenetes YAML is still under development!\n") - logrus.Warn("This function is still under heavy development.") - // Marshall the results - b, err := yaml.Marshal(podYAML) - if err != nil { - return err - } - output = append(output, developmentComment...) - output = append(output, b...) + header := `# Generation of Kubenetes YAML is still under development! +# +# Save the output of this file and use kubectl create -f to import +# it into Kubernetes. +# +# Created with podman-%s +` + output = append(output, []byte(fmt.Sprintf(header, podmanVersion.Version))...) + output = append(output, mashalledBytes...) // Output the v1.Pod with the v1.Container fmt.Println(string(output)) 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/kube.go b/cmd/podman/kube.go deleted file mode 100644 index 2cb407c09..000000000 --- a/cmd/podman/kube.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - -import ( - "github.com/urfave/cli" -) - -var ( - kubeSubCommands = []cli.Command{ - containerKubeCommand, - } - - kubeDescription = "Work with Kubernetes objects" - kubeCommand = cli.Command{ - Name: "kube", - Usage: "Import and export Kubernetes objections from and to Podman", - Description: containerDescription, - ArgsUsage: "", - Subcommands: kubeSubCommands, - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, - Hidden: true, - } -) diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index 0dc6bcf18..d7a0dd931 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -14,6 +14,11 @@ func GetRuntime(c *cli.Context) (*libpod.Runtime, error) { storageOpts := new(storage.StoreOptions) options := []libpod.RuntimeOption{} + _, volumePath, err := util.GetDefaultStoreOptions() + if err != nil { + return nil, err + } + 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 { @@ -90,6 +95,7 @@ func GetRuntime(c *cli.Context) (*libpod.Runtime, error) { 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 bcae04575..280448dc8 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "log/syslog" "os" "os/exec" "runtime/pprof" @@ -16,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 @@ -102,6 +102,7 @@ func main() { umountCommand, unpauseCommand, versionCommand, + volumeCommand, waitCommand, } 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/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 +} |