podmanv2 mount and umount

add the ability to mount and unmount containers for the local client only

Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
Brent Baude
2020-04-13 14:18:07 -05:00
parent 004826653f
commit d625aef0c5
6 changed files with 311 additions and 0 deletions

View File

@ -0,0 +1,121 @@
package containers
import (
"encoding/json"
"fmt"
"os"
"text/tabwriter"
"text/template"
"github.com/containers/libpod/cmd/podmanV2/parse"
"github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/cmd/podmanV2/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)
var (
mountDescription = `podman mount
Lists all mounted containers mount points if no container is specified
podman mount CONTAINER-NAME-OR-ID
Mounts the specified container and outputs the mountpoint
`
mountCommand = &cobra.Command{
Use: "mount [flags] [CONTAINER]",
Short: "Mount a working container's root filesystem",
Long: mountDescription,
PreRunE: preRunE,
RunE: mount,
Args: func(cmd *cobra.Command, args []string) error {
return parse.CheckAllLatestAndCIDFile(cmd, args, true, false)
},
}
)
var (
mountOpts entities.ContainerMountOptions
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode},
Command: mountCommand,
})
flags := mountCommand.Flags()
flags.BoolVarP(&mountOpts.All, "all", "a", false, "Mount all containers")
flags.StringVar(&mountOpts.Format, "format", "", "Change the output format to Go template")
flags.BoolVarP(&mountOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
flags.BoolVar(&mountOpts.NoTruncate, "notruncate", false, "Do not truncate output")
}
func mount(cmd *cobra.Command, args []string) error {
var (
errs utils.OutputErrors
mrs []mountReporter
)
reports, err := registry.ContainerEngine().ContainerMount(registry.GetContext(), args, mountOpts)
if err != nil {
return err
}
if len(args) > 0 || mountOpts.Latest || mountOpts.All {
for _, r := range reports {
if r.Err == nil {
fmt.Println(r.Path)
continue
}
errs = append(errs, r.Err)
}
return errs.PrintErrors()
}
if mountOpts.Format == "json" {
return printJSON(reports)
}
for _, r := range reports {
mrs = append(mrs, mountReporter{r})
}
row := "{{.ID}} {{.Path}}"
format := "{{range . }}" + row + "{{end}}"
tmpl, err := template.New("mounts").Parse(format)
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
defer w.Flush()
return tmpl.Execute(w, mrs)
}
func printJSON(reports []*entities.ContainerMountReport) error {
type jreport struct {
ID string `json:"id"`
Names []string
Mountpoint string `json:"mountpoint"`
}
var jreports []jreport
for _, r := range reports {
jreports = append(jreports, jreport{
ID: r.Id,
Names: []string{r.Name},
Mountpoint: r.Path,
})
}
b, err := json.MarshalIndent(jreports, "", " ")
if err != nil {
return err
}
fmt.Println(string(b))
return nil
}
type mountReporter struct {
*entities.ContainerMountReport
}
func (m mountReporter) ID() string {
if mountOpts.NoTruncate {
return m.Id
}
return m.Id[0:12]
}

View File

@ -0,0 +1,65 @@
package containers
import (
"fmt"
"github.com/containers/libpod/cmd/podmanV2/parse"
"github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/cmd/podmanV2/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)
var (
description = `Container storage increments a mount counter each time a container is mounted.
When a container is unmounted, the mount counter is decremented. The container's root filesystem is physically unmounted only when the mount counter reaches zero indicating no other processes are using the mount.
An unmount can be forced with the --force flag.
`
umountCommand = &cobra.Command{
Use: "umount [flags] CONTAINER [CONTAINER...]",
Aliases: []string{"unmount"},
Short: "Unmounts working container's root filesystem",
Long: description,
RunE: unmount,
PreRunE: preRunE,
Args: func(cmd *cobra.Command, args []string) error {
return parse.CheckAllLatestAndCIDFile(cmd, args, false, false)
},
Example: `podman umount ctrID
podman umount ctrID1 ctrID2 ctrID3
podman umount --all`,
}
)
var (
unmountOpts entities.ContainerUnmountOptions
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode},
Command: umountCommand,
})
flags := umountCommand.Flags()
flags.BoolVarP(&unmountOpts.All, "all", "a", false, "Umount all of the currently mounted containers")
flags.BoolVarP(&unmountOpts.Force, "force", "f", false, "Force the complete umount all of the currently mounted containers")
flags.BoolVarP(&unmountOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
}
func unmount(cmd *cobra.Command, args []string) error {
var errs utils.OutputErrors
reports, err := registry.ContainerEngine().ContainerUnmount(registry.GetContext(), args, unmountOpts)
if err != nil {
return err
}
for _, r := range reports {
if r.Err == nil {
fmt.Println(r.Id)
} else {
errs = append(errs, r.Err)
}
}
return errs.PrintErrors()
}

View File

@ -297,3 +297,33 @@ type ContainerInitReport struct {
Err error
Id string
}
//ContainerMountOptions describes the input values for mounting containers
// in the CLI
type ContainerMountOptions struct {
All bool
Format string
Latest bool
NoTruncate bool
}
// ContainerUnmountOptions are the options from the cli for unmounting
type ContainerUnmountOptions struct {
All bool
Force bool
Latest bool
}
// ContainerMountReport describes the response from container mount
type ContainerMountReport struct {
Err error
Id string
Name string
Path string
}
// ContainerUnmountReport describes the response from umounting a container
type ContainerUnmountReport struct {
Err error
Id string
}

View File

@ -21,6 +21,7 @@ type ContainerEngine interface {
ContainerInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]*ContainerInspectReport, error)
ContainerKill(ctx context.Context, namesOrIds []string, options KillOptions) ([]*KillReport, error)
ContainerList(ctx context.Context, options ContainerListOptions) ([]ListContainer, error)
ContainerMount(ctx context.Context, nameOrIds []string, options ContainerMountOptions) ([]*ContainerMountReport, error)
ContainerPause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error)
ContainerLogs(ctx context.Context, containers []string, options ContainerLogsOptions) error
ContainerRestart(ctx context.Context, namesOrIds []string, options RestartOptions) ([]*RestartReport, error)
@ -30,6 +31,7 @@ type ContainerEngine interface {
ContainerStart(ctx context.Context, namesOrIds []string, options ContainerStartOptions) ([]*ContainerStartReport, error)
ContainerStop(ctx context.Context, namesOrIds []string, options StopOptions) ([]*StopReport, error)
ContainerTop(ctx context.Context, options TopOptions) (*StringSliceReport, error)
ContainerUnmount(ctx context.Context, nameOrIds []string, options ContainerUnmountOptions) ([]*ContainerUnmountReport, error)
ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error)
ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error)
HealthCheckRun(ctx context.Context, nameOrId string, options HealthCheckOptions) (*define.HealthCheckResults, error)

View File

@ -6,6 +6,7 @@ import (
"context"
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"sync"
@ -21,9 +22,11 @@ import (
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/infra/abi/terminal"
"github.com/containers/libpod/pkg/ps"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/signal"
"github.com/containers/libpod/pkg/specgen"
"github.com/containers/libpod/pkg/specgen/generate"
"github.com/containers/storage"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@ -808,3 +811,85 @@ func (ic *ContainerEngine) ContainerInit(ctx context.Context, namesOrIds []strin
}
return reports, nil
}
func (ic *ContainerEngine) ContainerMount(ctx context.Context, nameOrIds []string, options entities.ContainerMountOptions) ([]*entities.ContainerMountReport, error) {
if os.Geteuid() != 0 {
if driver := ic.Libpod.StorageConfig().GraphDriverName; driver != "vfs" {
// Do not allow to mount a graphdriver that is not vfs if we are creating the userns as part
// of the mount command.
return nil, fmt.Errorf("cannot mount using driver %s in rootless mode", driver)
}
became, ret, err := rootless.BecomeRootInUserNS("")
if err != nil {
return nil, err
}
if became {
os.Exit(ret)
}
}
var reports []*entities.ContainerMountReport
ctrs, err := getContainersByContext(options.All, options.Latest, nameOrIds, ic.Libpod)
if err != nil {
return nil, err
}
for _, ctr := range ctrs {
report := entities.ContainerMountReport{Id: ctr.ID()}
report.Path, report.Err = ctr.Mount()
reports = append(reports, &report)
}
if len(reports) > 0 {
return reports, nil
}
// No containers were passed, so we send back what is mounted
ctrs, err = getContainersByContext(true, false, []string{}, ic.Libpod)
if err != nil {
return nil, err
}
for _, ctr := range ctrs {
mounted, path, err := ctr.Mounted()
if err != nil {
return nil, err
}
if mounted {
reports = append(reports, &entities.ContainerMountReport{
Id: ctr.ID(),
Name: ctr.Name(),
Path: path,
})
}
}
return reports, nil
}
func (ic *ContainerEngine) ContainerUnmount(ctx context.Context, nameOrIds []string, options entities.ContainerUnmountOptions) ([]*entities.ContainerUnmountReport, error) {
var reports []*entities.ContainerUnmountReport
ctrs, err := getContainersByContext(options.All, options.Latest, nameOrIds, ic.Libpod)
if err != nil {
return nil, err
}
for _, ctr := range ctrs {
state, err := ctr.State()
if err != nil {
logrus.Debugf("Error umounting container %s state: %s", ctr.ID(), err.Error())
continue
}
if state == define.ContainerStateRunning {
logrus.Debugf("Error umounting container %s, is running", ctr.ID())
continue
}
report := entities.ContainerUnmountReport{Id: ctr.ID()}
if err := ctr.Unmount(options.Force); err != nil {
if options.All && errors.Cause(err) == storage.ErrLayerNotMounted {
logrus.Debugf("Error umounting container %s, storage.ErrLayerNotMounted", ctr.ID())
continue
}
report.Err = errors.Wrapf(err, "error unmounting container %s", ctr.ID())
}
reports = append(reports, &report)
}
return reports, nil
}

View File

@ -354,3 +354,11 @@ func (ic *ContainerEngine) ContainerInit(ctx context.Context, namesOrIds []strin
}
return reports, nil
}
func (ic *ContainerEngine) ContainerMount(ctx context.Context, nameOrIds []string, options entities.ContainerMountOptions) ([]*entities.ContainerMountReport, error) {
return nil, errors.New("mounting containers is not supported for remote clients")
}
func (ic *ContainerEngine) ContainerUnmount(ctx context.Context, nameOrIds []string, options entities.ContainerUnmountOptions) ([]*entities.ContainerUnmountReport, error) {
return nil, errors.New("unmounting containers is not supported for remote clients")
}