diff options
31 files changed, 1261 insertions, 137 deletions
@@ -474,7 +474,7 @@ install: .gopathok install.bin install.remote install.man install.cni install.sy install.remote: podman-remote install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(BINDIR) install ${SELINUXOPT} -m 755 bin/podman-remote $(DESTDIR)$(BINDIR)/podman-remote - test -z "${SELINUXOPT}" || chcon --verbose --reference=$(DESTDIR)$(BINDIR)/podman bin/podman-remote + test -z "${SELINUXOPT}" || chcon --verbose --reference=$(DESTDIR)$(BINDIR)/podman-remote bin/podman-remote .PHONY: install.bin install.bin: podman diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index bf03463aa..12fd16551 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -384,6 +384,8 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string var command []string + s.Entrypoint = entrypoint + // Build the command // If we have an entry point, it goes first if len(entrypoint) > 0 { diff --git a/cmd/podman/containers/attach.go b/cmd/podman/containers/attach.go index 10ad80503..78b52ad1b 100644 --- a/cmd/podman/containers/attach.go +++ b/cmd/podman/containers/attach.go @@ -7,6 +7,7 @@ import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -26,18 +27,23 @@ var ( podman attach 1234 podman attach --no-stdin foobar`, } + + containerAttachCommand = &cobra.Command{ + Use: attachCommand.Use, + Short: attachCommand.Short, + Long: attachCommand.Long, + RunE: attachCommand.RunE, + Example: `podman container attach ctrID + podman container attach 1234 + podman container attach --no-stdin foobar`, + } ) var ( attachOpts entities.AttachOptions ) -func init() { - registry.Commands = append(registry.Commands, registry.CliCommand{ - Mode: []entities.EngineMode{entities.ABIMode}, - Command: attachCommand, - }) - flags := attachCommand.Flags() +func attachFlags(flags *pflag.FlagSet) { flags.StringVar(&attachOpts.DetachKeys, "detach-keys", containerConfig.DetachKeys(), "Select the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`") flags.BoolVar(&attachOpts.NoStdin, "no-stdin", false, "Do not attach STDIN. The default is false") flags.BoolVar(&attachOpts.SigProxy, "sig-proxy", true, "Proxy received signals to the process") @@ -47,6 +53,23 @@ func init() { } } +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: attachCommand, + }) + flags := attachCommand.Flags() + attachFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: containerAttachCommand, + Parent: containerCmd, + }) + containerAttachFlags := containerAttachCommand.Flags() + attachFlags(containerAttachFlags) +} + func attach(cmd *cobra.Command, args []string) error { attachOpts.Stdin = os.Stdin if attachOpts.NoStdin { diff --git a/cmd/podman/containers/commit.go b/cmd/podman/containers/commit.go index eaba07981..137e486eb 100644 --- a/cmd/podman/containers/commit.go +++ b/cmd/podman/containers/commit.go @@ -11,6 +11,7 @@ import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -28,6 +29,17 @@ var ( podman commit containerID`, } + containerCommitCommand = &cobra.Command{ + Use: commitCommand.Use, + Short: commitCommand.Short, + Long: commitCommand.Long, + RunE: commitCommand.RunE, + Example: `podman container commit -q --message "committing container to image" reverent_golick image-committed + podman container commit -q --author "firstName lastName" reverent_golick image-committed + podman container commit -q --pause=false containerID image-committed + podman container commit containerID`, + } + // ChangeCmds is the list of valid Changes commands to passed to the Commit call ChangeCmds = []string{"CMD", "ENTRYPOINT", "ENV", "EXPOSE", "LABEL", "ONBUILD", "STOPSIGNAL", "USER", "VOLUME", "WORKDIR"} ) @@ -39,12 +51,7 @@ var ( iidFile string ) -func init() { - registry.Commands = append(registry.Commands, registry.CliCommand{ - Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, - Command: commitCommand, - }) - flags := commitCommand.Flags() +func commitFlags(flags *pflag.FlagSet) { flags.StringArrayVarP(&commitOptions.Changes, "change", "c", []string{}, "Apply the following possible instructions to the created image (default []): "+strings.Join(ChangeCmds, " | ")) flags.StringVarP(&commitOptions.Format, "format", "f", "oci", "`Format` of the image manifest and metadata") flags.StringVarP(&iidFile, "iidfile", "", "", "`file` to write the image ID to") @@ -53,8 +60,25 @@ func init() { flags.BoolVarP(&commitOptions.Pause, "pause", "p", false, "Pause container during commit") flags.BoolVarP(&commitOptions.Quiet, "quiet", "q", false, "Suppress output") flags.BoolVar(&commitOptions.IncludeVolumes, "include-volumes", false, "Include container volumes as image volumes") +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: commitCommand, + }) + flags := commitCommand.Flags() + commitFlags(flags) + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: containerCommitCommand, + Parent: containerCmd, + }) + containerCommitFlags := containerCommitCommand.Flags() + commitFlags(containerCommitFlags) } + func commit(cmd *cobra.Command, args []string) error { container := args[0] if len(args) > 1 { diff --git a/cmd/podman/containers/cp.go b/cmd/podman/containers/cp.go new file mode 100644 index 000000000..f0f9a158d --- /dev/null +++ b/cmd/podman/containers/cp.go @@ -0,0 +1,55 @@ +package containers + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/cgroups" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/rootless" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + cpDescription = `Command copies the contents of SRC_PATH to the DEST_PATH. + + You can copy from the container's file system to the local machine or the reverse, from the local filesystem to the container. If "-" is specified for either the SRC_PATH or DEST_PATH, you can also stream a tar archive from STDIN or to STDOUT. The CONTAINER can be a running or stopped container. The SRC_PATH or DEST_PATH can be a file or directory. +` + cpCommand = &cobra.Command{ + Use: "cp [flags] SRC_PATH DEST_PATH", + Short: "Copy files/folders between a container and the local filesystem", + Long: cpDescription, + Args: cobra.ExactArgs(2), + RunE: cp, + Example: "podman cp [CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH", + } +) + +var ( + cpOpts entities.ContainerCpOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: cpCommand, + }) + flags := cpCommand.Flags() + flags.BoolVar(&cpOpts.Extract, "extract", false, "Extract the tar file into the destination directory.") + flags.BoolVar(&cpOpts.Pause, "pause", copyPause(), "Pause the container while copying") +} + +func cp(cmd *cobra.Command, args []string) error { + _, err := registry.ContainerEngine().ContainerCp(registry.GetContext(), args[0], args[1], cpOpts) + return err +} + +func copyPause() bool { + if rootless.IsRootless() { + cgroupv2, _ := cgroups.IsCgroup2UnifiedMode() + if !cgroupv2 { + logrus.Debugf("defaulting to pause==false on rootless cp in cgroupv1 systems") + return false + } + } + return true +} diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index 0843789eb..0c96f1a5c 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -27,12 +28,29 @@ var ( podman create --annotation HELLO=WORLD alpine ls podman create -t -i --name myctr alpine ls`, } + + containerCreateCommand = &cobra.Command{ + Use: createCommand.Use, + Short: createCommand.Short, + Long: createCommand.Long, + RunE: createCommand.RunE, + Example: `podman container create alpine ls + podman container create --annotation HELLO=WORLD alpine ls + podman container create -t -i --name myctr alpine ls`, + } ) var ( cliVals common.ContainerCLIOpts ) +func createFlags(flags *pflag.FlagSet) { + flags.SetInterspersed(false) + flags.AddFlagSet(common.GetCreateFlags(&cliVals)) + flags.AddFlagSet(common.GetNetFlags()) + flags.SetNormalizeFunc(common.AliasFlags) +} + func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, @@ -40,10 +58,16 @@ func init() { }) //common.GetCreateFlags(createCommand) flags := createCommand.Flags() - flags.SetInterspersed(false) - flags.AddFlagSet(common.GetCreateFlags(&cliVals)) - flags.AddFlagSet(common.GetNetFlags()) - flags.SetNormalizeFunc(common.AliasFlags) + createFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: containerCreateCommand, + Parent: containerCmd, + }) + + containerCreateFlags := containerCreateCommand.Flags() + createFlags(containerCreateFlags) } func create(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/exec.go b/cmd/podman/containers/exec.go index aaee4ee1c..3749c934a 100644 --- a/cmd/podman/containers/exec.go +++ b/cmd/podman/containers/exec.go @@ -9,6 +9,7 @@ import ( envLib "github.com/containers/libpod/pkg/env" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -23,6 +24,16 @@ var ( podman exec -it -w /tmp myCtr pwd podman exec --user root ctrID ls`, } + + containerExecCommand = &cobra.Command{ + Use: execCommand.Use, + Short: execCommand.Short, + Long: execCommand.Long, + RunE: execCommand.RunE, + Example: `podman container exec -it ctrID ls + podman container exec -it -w /tmp myCtr pwd + podman container exec --user root ctrID ls`, + } ) var ( @@ -30,12 +41,7 @@ var ( execOpts entities.ExecOptions ) -func init() { - registry.Commands = append(registry.Commands, registry.CliCommand{ - Mode: []entities.EngineMode{entities.ABIMode}, - Command: execCommand, - }) - flags := execCommand.Flags() +func execFlags(flags *pflag.FlagSet) { flags.SetInterspersed(false) flags.StringVar(&execOpts.DetachKeys, "detach-keys", containerConfig.DetachKeys(), "Select the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _") flags.StringArrayVarP(&envInput, "env", "e", []string{}, "Set environment variables") @@ -51,8 +57,26 @@ func init() { _ = flags.MarkHidden("latest") _ = flags.MarkHidden("preserve-fds") } +} +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: execCommand, + }) + flags := execCommand.Flags() + execFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: containerExecCommand, + Parent: containerCommitCommand, + }) + + containerExecFlags := containerExecCommand.Flags() + execFlags(containerExecFlags) } + func exec(cmd *cobra.Command, args []string) error { var nameOrId string execOpts.Cmd = args diff --git a/cmd/podman/containers/export.go b/cmd/podman/containers/export.go index 5110812d1..fb5bd468f 100644 --- a/cmd/podman/containers/export.go +++ b/cmd/podman/containers/export.go @@ -9,6 +9,7 @@ import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" "golang.org/x/crypto/ssh/terminal" ) @@ -25,19 +26,41 @@ var ( Example: `podman export ctrID > myCtr.tar podman export --output="myCtr.tar" ctrID`, } + + containerExportCommand = &cobra.Command{ + Use: exportCommand.Use, + Short: exportCommand.Short, + Long: exportCommand.Long, + RunE: exportCommand.RunE, + Example: `podman container export ctrID > myCtr.tar + podman container export --output="myCtr.tar" ctrID`, + } ) var ( exportOpts entities.ContainerExportOptions ) +func exportFlags(flags *pflag.FlagSet) { + flags.StringVarP(&exportOpts.Output, "output", "o", "", "Write to a specified file (default: stdout, which must be redirected)") +} + func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: exportCommand, }) flags := exportCommand.Flags() - flags.StringVarP(&exportOpts.Output, "output", "o", "", "Write to a specified file (default: stdout, which must be redirected)") + exportFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: containerExportCommand, + Parent: containerCmd, + }) + + containerExportFlags := containerExportCommand.Flags() + exportFlags(containerExportFlags) } func export(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/kill.go b/cmd/podman/containers/kill.go index 5341457fb..8b4a384fe 100644 --- a/cmd/podman/containers/kill.go +++ b/cmd/podman/containers/kill.go @@ -11,6 +11,7 @@ import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/signal" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -27,18 +28,23 @@ var ( podman kill 860a4b23 podman kill --signal TERM ctrID`, } + + containerKillCommand = &cobra.Command{ + Use: killCommand.Use, + Short: killCommand.Short, + Long: killCommand.Long, + RunE: killCommand.RunE, + Example: `podman container kill mywebserver + podman container kill 860a4b23 + podman container kill --signal TERM ctrID`, + } ) var ( killOptions = entities.KillOptions{} ) -func init() { - registry.Commands = append(registry.Commands, registry.CliCommand{ - Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, - Command: killCommand, - }) - flags := killCommand.Flags() +func killFlags(flags *pflag.FlagSet) { flags.BoolVarP(&killOptions.All, "all", "a", false, "Signal all running containers") flags.StringVarP(&killOptions.Signal, "signal", "s", "KILL", "Signal to send to the container") flags.BoolVarP(&killOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") @@ -47,6 +53,24 @@ func init() { } } +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: killCommand, + }) + flags := killCommand.Flags() + killFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: containerKillCommand, + Parent: containerCmd, + }) + + containerKillFlags := containerKillCommand.Flags() + killFlags(containerKillFlags) +} + func kill(cmd *cobra.Command, args []string) error { var ( err error diff --git a/cmd/podman/containers/mount.go b/cmd/podman/containers/mount.go index 25eec46ca..d0d132546 100644 --- a/cmd/podman/containers/mount.go +++ b/cmd/podman/containers/mount.go @@ -12,6 +12,7 @@ import ( "github.com/containers/libpod/cmd/podman/utils" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -34,22 +35,41 @@ var ( registry.ParentNSRequired: "", }, } + + containerMountCommmand = &cobra.Command{ + Use: mountCommand.Use, + Short: mountCommand.Short, + Long: mountCommand.Long, + RunE: mountCommand.RunE, + } ) var ( mountOpts entities.ContainerMountOptions ) +func mountFlags(flags *pflag.FlagSet) { + 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 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") + mountFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: containerMountCommmand, + Parent: containerCmd, + }) + containerMountFlags := containerMountCommmand.Flags() + mountFlags(containerMountFlags) } func mount(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/pause.go b/cmd/podman/containers/pause.go index f3654b5c1..b932c4539 100644 --- a/cmd/podman/containers/pause.go +++ b/cmd/podman/containers/pause.go @@ -10,6 +10,7 @@ import ( "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -24,16 +25,38 @@ var ( podman pause -a`, } + containerPauseCommand = &cobra.Command{ + Use: pauseCommand.Use, + Short: pauseCommand.Short, + Long: pauseCommand.Long, + RunE: pauseCommand.RunE, + Example: `podman container pause mywebserver + podman container pause 860a4b23 + podman container pause -a`, + } + pauseOpts = entities.PauseUnPauseOptions{} ) +func pauseFlags(flags *pflag.FlagSet) { + flags.BoolVarP(&pauseOpts.All, "all", "a", false, "Pause all running containers") +} + func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: pauseCommand, }) flags := pauseCommand.Flags() - flags.BoolVarP(&pauseOpts.All, "all", "a", false, "Pause all running containers") + pauseFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: containerPauseCommand, + Parent: containerCmd, + }) + containerPauseFlags := containerPauseCommand.Flags() + pauseFlags(containerPauseFlags) } func pause(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/port.go b/cmd/podman/containers/port.go new file mode 100644 index 000000000..0e50140ca --- /dev/null +++ b/cmd/podman/containers/port.go @@ -0,0 +1,123 @@ +package containers + +import ( + "fmt" + "strconv" + "strings" + + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + portDescription = `List port mappings for the CONTAINER, or lookup the public-facing port that is NAT-ed to the PRIVATE_PORT +` + portCommand = &cobra.Command{ + Use: "port [flags] CONTAINER [PORT]", + Short: "List port mappings or a specific mapping for the container", + Long: portDescription, + RunE: port, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, true, false) + }, + Example: `podman port --all + podman port ctrID 80/tcp + podman port --latest 80`, + } +) + +var ( + portOpts entities.ContainerPortOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: portCommand, + }) + flags := portCommand.Flags() + flags.BoolVarP(&portOpts.All, "all", "a", false, "Display port information for all containers") + flags.BoolVarP(&portOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} + +func port(cmd *cobra.Command, args []string) error { + var ( + container string + err error + userPort ocicni.PortMapping + ) + + if len(args) == 0 && !portOpts.Latest && !portOpts.All { + return errors.Errorf("you must supply a running container name or id") + } + if !portOpts.Latest && len(args) >= 1 { + container = args[0] + } + port := "" + if len(args) > 1 && !portOpts.Latest { + port = args[1] + } + if len(args) == 1 && portOpts.Latest { + port = args[0] + } + if len(port) > 0 { + fields := strings.Split(port, "/") + if len(fields) > 2 || len(fields) < 1 { + return errors.Errorf("port formats are port/protocol. '%s' is invalid", port) + } + if len(fields) == 1 { + fields = append(fields, "tcp") + } + + portNum, err := strconv.Atoi(fields[0]) + if err != nil { + return err + } + userPort = ocicni.PortMapping{ + HostPort: 0, + ContainerPort: int32(portNum), + Protocol: fields[1], + HostIP: "", + } + } + + reports, err := registry.ContainerEngine().ContainerPort(registry.GetContext(), container, portOpts) + if err != nil { + return err + } + var found bool + // Iterate mappings + for _, report := range reports { + for _, v := range report.Ports { + hostIP := v.HostIP + // Set host IP to 0.0.0.0 if blank + if hostIP == "" { + hostIP = "0.0.0.0" + } + if portOpts.All { + fmt.Printf("%s\t", report.Id[:12]) + } + // If not searching by port or port/proto, then dump what we see + if port == "" { + fmt.Printf("%d/%s -> %s:%d\n", v.ContainerPort, v.Protocol, hostIP, v.HostPort) + continue + } + if v == userPort { + fmt.Printf("%s:%d\n", hostIP, v.HostPort) + found = true + break + } + } + if !found && port != "" { + return errors.Errorf("failed to find published port %q", port) + } + } + return nil +} diff --git a/cmd/podman/containers/restart.go b/cmd/podman/containers/restart.go index 74df449a2..1a9d7f6c7 100644 --- a/cmd/podman/containers/restart.go +++ b/cmd/podman/containers/restart.go @@ -11,6 +11,7 @@ import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -30,6 +31,16 @@ var ( podman restart --latest podman restart ctrID1 ctrID2`, } + + containerRestartCommand = &cobra.Command{ + Use: restartCommand.Use, + Short: restartCommand.Short, + Long: restartCommand.Long, + RunE: restartCommand.RunE, + Example: `podman container restart ctrID + podman container restart --latest + podman container restart ctrID1 ctrID2`, + } ) var ( @@ -37,12 +48,7 @@ var ( restartTimeout uint ) -func init() { - registry.Commands = append(registry.Commands, registry.CliCommand{ - Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, - Command: restartCommand, - }) - flags := restartCommand.Flags() +func restartFlags(flags *pflag.FlagSet) { flags.BoolVarP(&restartOptions.All, "all", "a", false, "Restart all non-running containers") flags.BoolVarP(&restartOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") flags.BoolVar(&restartOptions.Running, "running", false, "Restart only running containers when --all is used") @@ -53,6 +59,24 @@ func init() { flags.SetNormalizeFunc(utils.AliasFlags) } +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: restartCommand, + }) + flags := restartCommand.Flags() + restartFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: containerRestartCommand, + Parent: containerCmd, + }) + + containerRestartFlags := containerRestartCommand.Flags() + restartFlags(containerRestartFlags) +} + func restart(cmd *cobra.Command, args []string) error { var ( errs utils.OutputErrors diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go index a22880d93..12a7a3d89 100644 --- a/cmd/podman/containers/rm.go +++ b/cmd/podman/containers/rm.go @@ -12,6 +12,7 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -31,18 +32,24 @@ var ( podman rm --force --all podman rm -f c684f0d469f2`, } + + containerRmCommand = &cobra.Command{ + Use: rmCommand.Use, + Short: rmCommand.Use, + Long: rmCommand.Long, + RunE: rmCommand.RunE, + Example: `podman container rm imageID + podman container rm mywebserver myflaskserver 860a4b23 + podman container rm --force --all + podman container rm -f c684f0d469f2`, + } ) var ( rmOptions = entities.RmOptions{} ) -func init() { - registry.Commands = append(registry.Commands, registry.CliCommand{ - Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, - Command: rmCommand, - }) - flags := rmCommand.Flags() +func rmFlags(flags *pflag.FlagSet) { flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all containers") flags.BoolVarP(&rmOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing") flags.BoolVarP(&rmOptions.Force, "force", "f", false, "Force removal of a running or unusable container. The default is false") @@ -56,7 +63,24 @@ func init() { _ = flags.MarkHidden("cidfile") _ = flags.MarkHidden("storage") } +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: rmCommand, + }) + flags := rmCommand.Flags() + rmFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: containerRmCommand, + Parent: containerCmd, + }) + containerRmFlags := containerRmCommand.Flags() + rmFlags(containerRmFlags) } func rm(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index 16a54e578..06b89b0fc 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -13,6 +13,7 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -26,6 +27,16 @@ var ( podman run --network=host imageID dnf -y install java podman run --volume /var/hostdir:/var/ctrdir -i -t fedora /bin/bash`, } + + containerRunCommand = &cobra.Command{ + Use: runCommand.Use, + Short: runCommand.Short, + Long: runCommand.Long, + RunE: runCommand.RunE, + Example: `podman container run imageID ls -alF /etc + podman container run --network=host imageID dnf -y install java + podman container run --volume /var/hostdir:/var/ctrdir -i -t fedora /bin/bash`, + } ) var ( @@ -37,12 +48,7 @@ var ( runRmi bool ) -func init() { - registry.Commands = append(registry.Commands, registry.CliCommand{ - Mode: []entities.EngineMode{entities.ABIMode}, - Command: runCommand, - }) - flags := runCommand.Flags() +func runFlags(flags *pflag.FlagSet) { flags.SetInterspersed(false) flags.AddFlagSet(common.GetCreateFlags(&cliVals)) flags.AddFlagSet(common.GetNetFlags()) @@ -53,6 +59,23 @@ func init() { _ = flags.MarkHidden("authfile") } } +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: runCommand, + }) + flags := runCommand.Flags() + runFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: containerRunCommand, + Parent: containerCmd, + }) + + containerRunFlags := containerRunCommand.Flags() + runFlags(containerRunFlags) +} func run(cmd *cobra.Command, args []string) error { var err error diff --git a/cmd/podman/containers/start.go b/cmd/podman/containers/start.go index f2ee0f177..73f37e51f 100644 --- a/cmd/podman/containers/start.go +++ b/cmd/podman/containers/start.go @@ -10,6 +10,7 @@ import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -24,18 +25,23 @@ var ( podman start 860a4b231279 5421ab43b45 podman start --interactive --attach imageID`, } + + containerStartCommand = &cobra.Command{ + Use: startCommand.Use, + Short: startCommand.Short, + Long: startCommand.Long, + RunE: startCommand.RunE, + Example: `podman container start --latest + podman container start 860a4b231279 5421ab43b45 + podman container start --interactive --attach imageID`, + } ) var ( startOptions entities.ContainerStartOptions ) -func init() { - registry.Commands = append(registry.Commands, registry.CliCommand{ - Mode: []entities.EngineMode{entities.ABIMode}, - Command: startCommand, - }) - flags := startCommand.Flags() +func startFlags(flags *pflag.FlagSet) { flags.BoolVarP(&startOptions.Attach, "attach", "a", false, "Attach container's STDOUT and STDERR") flags.StringVar(&startOptions.DetachKeys, "detach-keys", containerConfig.DetachKeys(), "Select the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`") flags.BoolVarP(&startOptions.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached") @@ -45,7 +51,23 @@ func init() { _ = flags.MarkHidden("latest") _ = flags.MarkHidden("sig-proxy") } +} +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: startCommand, + }) + flags := startCommand.Flags() + startFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: containerStartCommand, + Parent: containerCmd, + }) + containerStartFlags := containerStartCommand.Flags() + startFlags(containerStartFlags) } func start(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/stop.go b/cmd/podman/containers/stop.go index 1856524c9..4a451134a 100644 --- a/cmd/podman/containers/stop.go +++ b/cmd/podman/containers/stop.go @@ -9,6 +9,7 @@ import ( "github.com/containers/libpod/cmd/podman/utils" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -27,6 +28,16 @@ var ( podman stop --latest podman stop --time 2 mywebserver 6e534f14da9d`, } + + containerStopCommand = &cobra.Command{ + Use: stopCommand.Use, + Short: stopCommand.Short, + Long: stopCommand.Long, + RunE: stopCommand.RunE, + Example: `podman container stop ctrID + podman container stop --latest + podman container stop --time 2 mywebserver 6e534f14da9d`, + } ) var ( @@ -34,12 +45,7 @@ var ( stopTimeout uint ) -func init() { - registry.Commands = append(registry.Commands, registry.CliCommand{ - Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, - Command: stopCommand, - }) - flags := stopCommand.Flags() +func stopFlags(flags *pflag.FlagSet) { flags.BoolVarP(&stopOptions.All, "all", "a", false, "Stop all running containers") flags.BoolVarP(&stopOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing") flags.StringArrayVarP(&stopOptions.CIDFiles, "cidfile", "", nil, "Read the container ID from the file") @@ -54,6 +60,24 @@ func init() { flags.SetNormalizeFunc(utils.AliasFlags) } +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: stopCommand, + }) + flags := stopCommand.Flags() + stopFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: containerStopCommand, + Parent: containerCmd, + }) + + containerStopFlags := containerStopCommand.Flags() + stopFlags(containerStopFlags) +} + func stop(cmd *cobra.Command, args []string) error { var ( errs utils.OutputErrors diff --git a/cmd/podman/containers/top.go b/cmd/podman/containers/top.go index db5213863..732a08623 100644 --- a/cmd/podman/containers/top.go +++ b/cmd/podman/containers/top.go @@ -12,6 +12,7 @@ import ( "github.com/containers/psgo" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -36,25 +37,46 @@ podman top --latest podman top ctrID pid seccomp args %C podman top ctrID -eo user,pid,comm`, } -) -func init() { - registry.Commands = append(registry.Commands, registry.CliCommand{ - Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, - Command: topCommand, - }) + containerTopCommand = &cobra.Command{ + Use: topCommand.Use, + Short: topCommand.Short, + Long: topCommand.Long, + RunE: topCommand.RunE, + Example: `podman container top ctrID +podman container top --latest +podman container top ctrID pid seccomp args %C +podman container top ctrID -eo user,pid,comm`, + } +) - flags := topCommand.Flags() +func topFlags(flags *pflag.FlagSet) { flags.SetInterspersed(false) flags.BoolVar(&topOptions.ListDescriptors, "list-descriptors", false, "") flags.BoolVarP(&topOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - _ = flags.MarkHidden("list-descriptors") // meant only for bash completion if registry.IsRemote() { _ = flags.MarkHidden("latest") } } +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: topCommand, + }) + flags := topCommand.Flags() + topFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: containerTopCommand, + Parent: containerCmd, + }) + containerTopFlags := containerTopCommand.Flags() + topFlags(containerTopFlags) +} + func top(cmd *cobra.Command, args []string) error { if topOptions.ListDescriptors { fmt.Println(strings.Join(psgo.ListDescriptors(), "\n")) diff --git a/cmd/podman/containers/unmount.go b/cmd/podman/containers/unmount.go index 3dbfc1eae..a4550abbd 100644 --- a/cmd/podman/containers/unmount.go +++ b/cmd/podman/containers/unmount.go @@ -8,6 +8,7 @@ import ( "github.com/containers/libpod/cmd/podman/utils" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -30,21 +31,44 @@ var ( podman umount ctrID1 ctrID2 ctrID3 podman umount --all`, } + + containerUnmountCommand = &cobra.Command{ + Use: umountCommand.Use, + Short: umountCommand.Short, + Long: umountCommand.Long, + RunE: umountCommand.RunE, + Example: `podman container umount ctrID + podman container umount ctrID1 ctrID2 ctrID3 + podman container umount --all`, + } ) var ( unmountOpts entities.ContainerUnmountOptions ) +func umountFlags(flags *pflag.FlagSet) { + 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 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") + umountFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: containerUnmountCommand, + Parent: containerCmd, + }) + + containerUmountFlags := containerUnmountCommand.Flags() + umountFlags(containerUmountFlags) } func unmount(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/unpause.go b/cmd/podman/containers/unpause.go index ef874b042..adf8d12ee 100644 --- a/cmd/podman/containers/unpause.go +++ b/cmd/podman/containers/unpause.go @@ -10,6 +10,7 @@ import ( "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -23,16 +24,37 @@ var ( podman unpause --all`, } unPauseOptions = entities.PauseUnPauseOptions{} + + containerUnpauseCommand = &cobra.Command{ + Use: unpauseCommand.Use, + Short: unpauseCommand.Short, + Long: unpauseCommand.Long, + RunE: unpauseCommand.RunE, + Example: `podman container unpause ctrID + podman container unpause --all`, + } ) +func unpauseFlags(flags *pflag.FlagSet) { + flags.BoolVarP(&unPauseOptions.All, "all", "a", false, "Pause all running containers") +} + func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: unpauseCommand, - Parent: containerCmd, }) flags := unpauseCommand.Flags() - flags.BoolVarP(&unPauseOptions.All, "all", "a", false, "Pause all running containers") + unpauseFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: unpauseCommand, + Parent: containerCmd, + }) + + unpauseCommandFlags := containerUnpauseCommand.Flags() + unpauseFlags(unpauseCommandFlags) } func unpause(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/wait.go b/cmd/podman/containers/wait.go index 47f28f4c6..da746361d 100644 --- a/cmd/podman/containers/wait.go +++ b/cmd/podman/containers/wait.go @@ -11,6 +11,7 @@ import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -26,6 +27,16 @@ var ( podman wait --interval 5000 ctrID podman wait ctrID1 ctrID2`, } + + containerWaitCommand = &cobra.Command{ + Use: waitCommand.Use, + Short: waitCommand.Short, + Long: waitCommand.Long, + RunE: waitCommand.RunE, + Example: `podman container wait --latest + podman container wait --interval 5000 ctrID + podman container wait ctrID1 ctrID2`, + } ) var ( @@ -33,13 +44,7 @@ var ( waitCondition string ) -func init() { - registry.Commands = append(registry.Commands, registry.CliCommand{ - Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, - Command: waitCommand, - }) - - flags := waitCommand.Flags() +func waitFlags(flags *pflag.FlagSet) { flags.DurationVarP(&waitOptions.Interval, "interval", "i", time.Duration(250), "Milliseconds to wait before polling for completion") flags.BoolVarP(&waitOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") flags.StringVar(&waitCondition, "condition", "stopped", "Condition to wait on") @@ -49,6 +54,24 @@ func init() { } } +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: waitCommand, + }) + flags := waitCommand.Flags() + waitFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: containerWaitCommand, + Parent: containerCmd, + }) + + containerWaitFlags := containerWaitCommand.Flags() + waitFlags(containerWaitFlags) +} + func wait(cmd *cobra.Command, args []string) error { var ( err error diff --git a/cmd/podman/images/inspect.go b/cmd/podman/images/inspect.go index 11bef02ba..3190ab725 100644 --- a/cmd/podman/images/inspect.go +++ b/cmd/podman/images/inspect.go @@ -84,10 +84,14 @@ func inspect(cmd *cobra.Command, args []string) error { } } + var lastErr error for id, e := range results.Errors { - fmt.Fprintf(os.Stderr, "%s: %s\n", id, e.Error()) + if lastErr != nil { + fmt.Fprintf(os.Stderr, "%s: %s\n", id, lastErr.Error()) + } + lastErr = e } - return nil + return lastErr } func inspectFormat(row string) string { diff --git a/contrib/dependencies.txt b/contrib/dependencies.txt new file mode 100644 index 000000000..5a6fa9834 --- /dev/null +++ b/contrib/dependencies.txt @@ -0,0 +1,34 @@ +# Fedora dependencies for building podman + +btrfs-progs-devel +bzip2 +container-selinux +containernetworking-cni +device-mapper-devel +findutils +git +glib2-devel +glibc-static +golang +gpgme-devel +iptables +libassuan-devel +libseccomp-devel +libselinux-devel +lsof +make +nmap-ncat +procps-ng +python +python3-dateutil +python3-pip +python3-psutil +python3-pytoml +python3-pyyaml +python3-varlink +rsync +slirp4netns +unzip +which +xz +zip diff --git a/contrib/gate/Dockerfile b/contrib/gate/Dockerfile index 4fddae557..f7cd8f2b3 100644 --- a/contrib/gate/Dockerfile +++ b/contrib/gate/Dockerfile @@ -1,38 +1,4 @@ FROM fedora:31 -RUN dnf -y install \ - btrfs-progs-devel \ - bzip2 \ - container-selinux \ - containernetworking-cni \ - device-mapper-devel \ - findutils \ - git \ - glib2-devel \ - glibc-static \ - golang \ - gpgme-devel \ - iptables \ - libassuan-devel \ - libseccomp-devel \ - libselinux-devel \ - lsof \ - make \ - nmap-ncat \ - procps-ng \ - python \ - python3-dateutil \ - python3-pip \ - python3-psutil \ - python3-pytoml \ - python3-pyyaml \ - python3-varlink \ - rsync \ - slirp4netns \ - unzip \ - which \ - xz \ - zip \ - && dnf clean all ENV GOPATH="/var/tmp/go" \ GOBIN="/var/tmp/go/bin" \ @@ -43,6 +9,11 @@ ENV GOPATH="/var/tmp/go" \ # Only needed for installing build-time dependencies, then will be removed COPY / $GOSRC +# Install packages from dependencies.txt, ignoring commented lines +RUN dnf -y install \ + $(grep "^[^#]" $GOSRC/contrib/dependencies.txt) \ + && dnf clean all + # Install dependencies RUN set -x && \ mkdir -p "$GOBIN" && \ diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 52327a905..e58258b75 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -8,6 +8,7 @@ import ( "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/specgen" + "github.com/cri-o/ocicni/pkg/ocicni" ) type WaitOptions struct { @@ -341,3 +342,27 @@ type ContainerPruneReport struct { ID map[string]int64 Err map[string]error } + +// ContainerPortOptions describes the options to obtain +// port information on containers +type ContainerPortOptions struct { + All bool + Latest bool +} + +// ContainerPortReport describes the output needed for +// the CLI to output ports +type ContainerPortReport struct { + Id string + Ports []ocicni.PortMapping +} + +// ContainerCpOptions describes input options for cp +type ContainerCpOptions struct { + Pause bool + Extract bool +} + +// ContainerCpReport describes the output from a cp operation +type ContainerCpReport struct { +} diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 506d1c317..60833d879 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -16,6 +16,7 @@ type ContainerEngine interface { ContainerCleanup(ctx context.Context, namesOrIds []string, options ContainerCleanupOptions) ([]*ContainerCleanupReport, error) ContainerPrune(ctx context.Context, options ContainerPruneOptions) (*ContainerPruneReport, error) ContainerCommit(ctx context.Context, nameOrId string, options CommitOptions) (*CommitReport, error) + ContainerCp(ctx context.Context, source, dest string, options ContainerCpOptions) (*ContainerCpReport, error) ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error) ContainerDiff(ctx context.Context, nameOrId string, options DiffOptions) (*DiffReport, error) ContainerExec(ctx context.Context, nameOrId string, options ExecOptions) (int, error) @@ -28,6 +29,7 @@ type ContainerEngine interface { ContainerLogs(ctx context.Context, containers []string, options ContainerLogsOptions) error ContainerMount(ctx context.Context, nameOrIds []string, options ContainerMountOptions) ([]*ContainerMountReport, error) ContainerPause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error) + ContainerPort(ctx context.Context, nameOrId string, options ContainerPortOptions) ([]*ContainerPortReport, error) ContainerRestart(ctx context.Context, namesOrIds []string, options RestartOptions) ([]*RestartReport, error) ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error) ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error) diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index e71ceb536..73a0d8ec3 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -929,3 +929,31 @@ func (ic *ContainerEngine) ContainerUnmount(ctx context.Context, nameOrIds []str func (ic *ContainerEngine) Config(_ context.Context) (*config.Config, error) { return ic.Libpod.GetConfig() } + +func (ic *ContainerEngine) ContainerPort(ctx context.Context, nameOrId string, options entities.ContainerPortOptions) ([]*entities.ContainerPortReport, error) { + var reports []*entities.ContainerPortReport + ctrs, err := getContainersByContext(options.All, false, []string{nameOrId}, ic.Libpod) + if err != nil { + return nil, err + } + for _, con := range ctrs { + state, err := con.State() + if err != nil { + return nil, err + } + if state != define.ContainerStateRunning { + continue + } + portmappings, err := con.PortMappings() + if err != nil { + return nil, err + } + if len(portmappings) > 0 { + reports = append(reports, &entities.ContainerPortReport{ + Id: con.ID(), + Ports: portmappings, + }) + } + } + return reports, nil +} diff --git a/pkg/domain/infra/abi/cp.go b/pkg/domain/infra/abi/cp.go new file mode 100644 index 000000000..9fc1e3bee --- /dev/null +++ b/pkg/domain/infra/abi/cp.go @@ -0,0 +1,433 @@ +package abi + +import ( + "archive/tar" + "context" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/containers/buildah/pkg/chrootuser" + "github.com/containers/buildah/util" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/storage" + "github.com/containers/storage/pkg/chrootarchive" + "github.com/containers/storage/pkg/idtools" + securejoin "github.com/cyphar/filepath-securejoin" + "github.com/docker/docker/pkg/archive" + "github.com/opencontainers/go-digest" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, options entities.ContainerCpOptions) (*entities.ContainerCpReport, error) { + var extract bool + + srcCtr, srcPath := parsePath(ic.Libpod, source) + destCtr, destPath := parsePath(ic.Libpod, dest) + + if (srcCtr == nil && destCtr == nil) || (srcCtr != nil && destCtr != nil) { + return nil, errors.Errorf("invalid arguments %s, %s you must use just one container", source, dest) + } + + if len(srcPath) == 0 || len(destPath) == 0 { + return nil, errors.Errorf("invalid arguments %s, %s you must specify paths", source, dest) + } + ctr := srcCtr + isFromHostToCtr := ctr == nil + if isFromHostToCtr { + ctr = destCtr + } + + mountPoint, err := ctr.Mount() + if err != nil { + return nil, err + } + defer func() { + if err := ctr.Unmount(false); err != nil { + logrus.Errorf("unable to umount container '%s': %q", ctr.ID(), err) + } + }() + + if options.Pause { + if err := ctr.Pause(); err != nil { + // An invalid state error is fine. + // The container isn't running or is already paused. + // TODO: We can potentially start the container while + // the copy is running, which still allows a race where + // malicious code could mess with the symlink. + if errors.Cause(err) != define.ErrCtrStateInvalid { + return nil, err + } + } else { + // Only add the defer if we actually paused + defer func() { + if err := ctr.Unpause(); err != nil { + logrus.Errorf("Error unpausing container after copying: %v", err) + } + }() + } + } + + user, err := getUser(mountPoint, ctr.User()) + if err != nil { + return nil, err + } + idMappingOpts, err := ctr.IDMappings() + if err != nil { + return nil, errors.Wrapf(err, "error getting IDMappingOptions") + } + destOwner := idtools.IDPair{UID: int(user.UID), GID: int(user.GID)} + hostUID, hostGID, err := util.GetHostIDs(convertIDMap(idMappingOpts.UIDMap), convertIDMap(idMappingOpts.GIDMap), user.UID, user.GID) + if err != nil { + return nil, err + } + + hostOwner := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)} + + if isFromHostToCtr { + if isVol, volDestName, volName := isVolumeDestName(destPath, ctr); isVol { //nolint(gocritic) + path, err := pathWithVolumeMount(ctr, ic.Libpod, volDestName, volName, destPath) + if err != nil { + return nil, errors.Wrapf(err, "error getting destination path from volume %s", volDestName) + } + destPath = path + } else if isBindMount, mount := isBindMountDestName(destPath, ctr); isBindMount { //nolint(gocritic) + path, err := pathWithBindMountSource(mount, destPath) + if err != nil { + return nil, errors.Wrapf(err, "error getting destination path from bind mount %s", mount.Destination) + } + destPath = path + } else if filepath.IsAbs(destPath) { //nolint(gocritic) + cleanedPath, err := securejoin.SecureJoin(mountPoint, destPath) + if err != nil { + return nil, err + } + destPath = cleanedPath + } else { //nolint(gocritic) + ctrWorkDir, err := securejoin.SecureJoin(mountPoint, ctr.WorkingDir()) + if err != nil { + return nil, err + } + if err = idtools.MkdirAllAndChownNew(ctrWorkDir, 0755, hostOwner); err != nil { + return nil, errors.Wrapf(err, "error creating directory %q", destPath) + } + cleanedPath, err := securejoin.SecureJoin(mountPoint, filepath.Join(ctr.WorkingDir(), destPath)) + if err != nil { + return nil, err + } + destPath = cleanedPath + } + } else { + destOwner = idtools.IDPair{UID: os.Getuid(), GID: os.Getgid()} + if isVol, volDestName, volName := isVolumeDestName(srcPath, ctr); isVol { //nolint(gocritic) + path, err := pathWithVolumeMount(ctr, ic.Libpod, volDestName, volName, srcPath) + if err != nil { + return nil, errors.Wrapf(err, "error getting source path from volume %s", volDestName) + } + srcPath = path + } else if isBindMount, mount := isBindMountDestName(srcPath, ctr); isBindMount { //nolint(gocritic) + path, err := pathWithBindMountSource(mount, srcPath) + if err != nil { + return nil, errors.Wrapf(err, "error getting source path from bind mount %s", mount.Destination) + } + srcPath = path + } else if filepath.IsAbs(srcPath) { //nolint(gocritic) + cleanedPath, err := securejoin.SecureJoin(mountPoint, srcPath) + if err != nil { + return nil, err + } + srcPath = cleanedPath + } else { //nolint(gocritic) + cleanedPath, err := securejoin.SecureJoin(mountPoint, filepath.Join(ctr.WorkingDir(), srcPath)) + if err != nil { + return nil, err + } + srcPath = cleanedPath + } + } + + if !filepath.IsAbs(destPath) { + dir, err := os.Getwd() + if err != nil { + return nil, errors.Wrapf(err, "err getting current working directory") + } + destPath = filepath.Join(dir, destPath) + } + + if source == "-" { + srcPath = os.Stdin.Name() + extract = true + } + err = containerCopy(srcPath, destPath, source, dest, idMappingOpts, &destOwner, extract, isFromHostToCtr) + return &entities.ContainerCpReport{}, err +} + +func getUser(mountPoint string, userspec string) (specs.User, error) { + uid, gid, _, err := chrootuser.GetUser(mountPoint, userspec) + u := specs.User{ + UID: uid, + GID: gid, + Username: userspec, + } + if !strings.Contains(userspec, ":") { + groups, err2 := chrootuser.GetAdditionalGroupsForUser(mountPoint, uint64(u.UID)) + if err2 != nil { + if errors.Cause(err2) != chrootuser.ErrNoSuchUser && err == nil { + err = err2 + } + } else { + u.AdditionalGids = groups + } + + } + return u, err +} + +func parsePath(runtime *libpod.Runtime, path string) (*libpod.Container, string) { + pathArr := strings.SplitN(path, ":", 2) + if len(pathArr) == 2 { + ctr, err := runtime.LookupContainer(pathArr[0]) + if err == nil { + return ctr, pathArr[1] + } + } + return nil, path +} + +func evalSymlinks(path string) (string, error) { + if path == os.Stdin.Name() { + return path, nil + } + return filepath.EvalSymlinks(path) +} + +func getPathInfo(path string) (string, os.FileInfo, error) { + path, err := evalSymlinks(path) + if err != nil { + return "", nil, errors.Wrapf(err, "error evaluating symlinks %q", path) + } + srcfi, err := os.Stat(path) + if err != nil { + return "", nil, errors.Wrapf(err, "error reading path %q", path) + } + return path, srcfi, nil +} + +func containerCopy(srcPath, destPath, src, dest string, idMappingOpts storage.IDMappingOptions, chownOpts *idtools.IDPair, extract, isFromHostToCtr bool) error { + srcPath, err := evalSymlinks(srcPath) + if err != nil { + return errors.Wrapf(err, "error evaluating symlinks %q", srcPath) + } + + srcPath, srcfi, err := getPathInfo(srcPath) + if err != nil { + return err + } + + filename := filepath.Base(destPath) + if filename == "-" && !isFromHostToCtr { + err := streamFileToStdout(srcPath, srcfi) + if err != nil { + return errors.Wrapf(err, "error streaming source file %s to Stdout", srcPath) + } + return nil + } + + destdir := destPath + if !srcfi.IsDir() { + destdir = filepath.Dir(destPath) + } + _, err = os.Stat(destdir) + if err != nil && !os.IsNotExist(err) { + return errors.Wrapf(err, "error checking directory %q", destdir) + } + destDirIsExist := err == nil + if err = os.MkdirAll(destdir, 0755); err != nil { + return errors.Wrapf(err, "error creating directory %q", destdir) + } + + // return functions for copying items + copyFileWithTar := chrootarchive.CopyFileWithTarAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap) + copyWithTar := chrootarchive.CopyWithTarAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap) + untarPath := chrootarchive.UntarPathAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap) + + if srcfi.IsDir() { + logrus.Debugf("copying %q to %q", srcPath+string(os.PathSeparator)+"*", dest+string(os.PathSeparator)+"*") + if destDirIsExist && !strings.HasSuffix(src, fmt.Sprintf("%s.", string(os.PathSeparator))) { + destPath = filepath.Join(destPath, filepath.Base(srcPath)) + } + if err = copyWithTar(srcPath, destPath); err != nil { + return errors.Wrapf(err, "error copying %q to %q", srcPath, dest) + } + return nil + } + + if extract { + // We're extracting an archive into the destination directory. + logrus.Debugf("extracting contents of %q into %q", srcPath, destPath) + if err = untarPath(srcPath, destPath); err != nil { + return errors.Wrapf(err, "error extracting %q into %q", srcPath, destPath) + } + return nil + } + + destfi, err := os.Stat(destPath) + if err != nil { + if !os.IsNotExist(err) || strings.HasSuffix(dest, string(os.PathSeparator)) { + return errors.Wrapf(err, "failed to get stat of dest path %s", destPath) + } + } + if destfi != nil && destfi.IsDir() { + destPath = filepath.Join(destPath, filepath.Base(srcPath)) + } + + // Copy the file, preserving attributes. + logrus.Debugf("copying %q to %q", srcPath, destPath) + if err = copyFileWithTar(srcPath, destPath); err != nil { + return errors.Wrapf(err, "error copying %q to %q", srcPath, destPath) + } + return nil +} + +func convertIDMap(idMaps []idtools.IDMap) (convertedIDMap []specs.LinuxIDMapping) { + for _, idmap := range idMaps { + tempIDMap := specs.LinuxIDMapping{ + ContainerID: uint32(idmap.ContainerID), + HostID: uint32(idmap.HostID), + Size: uint32(idmap.Size), + } + convertedIDMap = append(convertedIDMap, tempIDMap) + } + return convertedIDMap +} + +func streamFileToStdout(srcPath string, srcfi os.FileInfo) error { + if srcfi.IsDir() { + tw := tar.NewWriter(os.Stdout) + err := filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error { + if err != nil || !info.Mode().IsRegular() || path == srcPath { + return err + } + hdr, err := tar.FileInfoHeader(info, "") + if err != nil { + return err + } + + if err = tw.WriteHeader(hdr); err != nil { + return err + } + fh, err := os.Open(path) + if err != nil { + return err + } + defer fh.Close() + + _, err = io.Copy(tw, fh) + return err + }) + if err != nil { + return errors.Wrapf(err, "error streaming directory %s to Stdout", srcPath) + } + return nil + } + + file, err := os.Open(srcPath) + if err != nil { + return errors.Wrapf(err, "error opening file %s", srcPath) + } + defer file.Close() + if !archive.IsArchivePath(srcPath) { + tw := tar.NewWriter(os.Stdout) + hdr, err := tar.FileInfoHeader(srcfi, "") + if err != nil { + return err + } + err = tw.WriteHeader(hdr) + if err != nil { + return err + } + _, err = io.Copy(tw, file) + if err != nil { + return errors.Wrapf(err, "error streaming archive %s to Stdout", srcPath) + } + return nil + } + + _, err = io.Copy(os.Stdout, file) + if err != nil { + return errors.Wrapf(err, "error streaming file to Stdout") + } + return nil +} + +func isVolumeDestName(path string, ctr *libpod.Container) (bool, string, string) { + separator := string(os.PathSeparator) + if filepath.IsAbs(path) { + path = strings.TrimPrefix(path, separator) + } + if path == "" { + return false, "", "" + } + for _, vol := range ctr.Config().NamedVolumes { + volNamePath := strings.TrimPrefix(vol.Dest, separator) + if matchVolumePath(path, volNamePath) { + return true, vol.Dest, vol.Name + } + } + return false, "", "" +} + +// if SRCPATH or DESTPATH is from volume mount's destination -v or --mount type=volume, generates the path with volume mount point +func pathWithVolumeMount(ctr *libpod.Container, runtime *libpod.Runtime, volDestName, volName, path string) (string, error) { + destVolume, err := runtime.GetVolume(volName) + if err != nil { + return "", errors.Wrapf(err, "error getting volume destination %s", volName) + } + if !filepath.IsAbs(path) { + path = filepath.Join(string(os.PathSeparator), path) + } + path, err = securejoin.SecureJoin(destVolume.MountPoint(), strings.TrimPrefix(path, volDestName)) + return path, err +} + +func isBindMountDestName(path string, ctr *libpod.Container) (bool, specs.Mount) { + separator := string(os.PathSeparator) + if filepath.IsAbs(path) { + path = strings.TrimPrefix(path, string(os.PathSeparator)) + } + if path == "" { + return false, specs.Mount{} + } + for _, m := range ctr.Config().Spec.Mounts { + if m.Type != "bind" { + continue + } + mDest := strings.TrimPrefix(m.Destination, separator) + if matchVolumePath(path, mDest) { + return true, m + } + } + return false, specs.Mount{} +} + +func matchVolumePath(path, target string) bool { + pathStr := filepath.Clean(path) + target = filepath.Clean(target) + for len(pathStr) > len(target) && strings.Contains(pathStr, string(os.PathSeparator)) { + pathStr = pathStr[:strings.LastIndex(pathStr, string(os.PathSeparator))] + } + return pathStr == target +} + +func pathWithBindMountSource(m specs.Mount, path string) (string, error) { + if !filepath.IsAbs(path) { + path = filepath.Join(string(os.PathSeparator), path) + } + return securejoin.SecureJoin(m.Source, strings.TrimPrefix(path, m.Destination)) +} diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 679bb371b..8867ce27f 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -371,3 +371,11 @@ func (ic *ContainerEngine) ContainerUnmount(ctx context.Context, nameOrIds []str func (ic *ContainerEngine) Config(_ context.Context) (*config.Config, error) { return config.Default() } + +func (ic *ContainerEngine) ContainerPort(ctx context.Context, nameOrId string, options entities.ContainerPortOptions) ([]*entities.ContainerPortReport, error) { + return nil, errors.New("not implemented") +} + +func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, options entities.ContainerCpOptions) (*entities.ContainerCpReport, error) { + return nil, errors.New("not implemented") +} diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index 31465d8bf..8c02731fd 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -13,6 +13,7 @@ import ( ) func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerator) error { + var appendEntryPoint bool newImage, err := r.ImageRuntime().NewFromLocal(s.Image) if err != nil { @@ -64,6 +65,16 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat } // annotations + + // Add annotations from the image + annotations, err := newImage.Annotations(ctx) + if err != nil { + return err + } + for k, v := range annotations { + annotations[k] = v + } + // in the event this container is in a pod, and the pod has an infra container // we will want to configure it as a type "container" instead defaulting to // the behavior of a "sandbox" container @@ -72,20 +83,17 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat // VM, which is the default behavior // - "container" denotes the container should join the VM of the SandboxID // (the infra container) - s.Annotations = make(map[string]string) + if len(s.Pod) > 0 { - s.Annotations[ann.SandboxID] = s.Pod - s.Annotations[ann.ContainerType] = ann.ContainerTypeContainer - } - // - // Next, add annotations from the image - annotations, err := newImage.Annotations(ctx) - if err != nil { - return err + annotations[ann.SandboxID] = s.Pod + annotations[ann.ContainerType] = ann.ContainerTypeContainer } - for k, v := range annotations { + + // now pass in the values from client + for k, v := range s.Annotations { annotations[k] = v } + s.Annotations = annotations // entrypoint entrypoint, err := newImage.Entrypoint(ctx) @@ -93,6 +101,7 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat return err } if len(s.Entrypoint) < 1 && len(entrypoint) > 0 { + appendEntryPoint = true s.Entrypoint = entrypoint } command, err := newImage.Cmd(ctx) @@ -100,7 +109,10 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat return err } if len(s.Command) < 1 && len(command) > 0 { - s.Command = command + if appendEntryPoint { + s.Command = entrypoint + } + s.Command = append(s.Command, command...) } if len(s.Command) < 1 && len(s.Entrypoint) < 1 { return errors.Errorf("No command provided or as CMD or ENTRYPOINT in this image") diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index 0ed091f9a..fc269cd44 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -12,6 +12,42 @@ import ( "github.com/opencontainers/runtime-tools/generate" ) +func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error { + var ( + kernelMax uint64 = 1048576 + isRootless = rootless.IsRootless() + nofileSet = false + nprocSet = false + ) + + if s.Rlimits == nil { + g.Config.Process.Rlimits = nil + return nil + } + + for _, u := range s.Rlimits { + name := "RLIMIT_" + strings.ToUpper(u.Type) + if name == "RLIMIT_NOFILE" { + nofileSet = true + } else if name == "RLIMIT_NPROC" { + nprocSet = true + } + g.AddProcessRlimits(name, u.Hard, u.Soft) + } + + // If not explicitly overridden by the user, default number of open + // files and number of processes to the maximum they can be set to + // (without overriding a sysctl) + if !nofileSet && !isRootless { + g.AddProcessRlimits("RLIMIT_NOFILE", kernelMax, kernelMax) + } + if !nprocSet && !isRootless { + g.AddProcessRlimits("RLIMIT_NPROC", kernelMax, kernelMax) + } + + return nil +} + func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image.Image) (*spec.Spec, error) { var ( inUserNS bool @@ -176,11 +212,9 @@ func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image. g.AddProcessEnv(name, val) } - // TODO rlimits and ulimits needs further refinement by someone more - // familiar with the code. - //if err := addRlimits(config, &g); err != nil { - // return nil, err - //} + if err := addRlimits(s, &g); err != nil { + return nil, err + } // NAMESPACES |