summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--cmd/podman/containers/commit.go6
-rw-r--r--cmd/podman/containers/export.go2
-rw-r--r--cmd/podman/containers/inspect.go2
-rw-r--r--cmd/podman/containers/mount.go2
-rw-r--r--cmd/podman/containers/port.go3
-rw-r--r--cmd/podman/containers/ps.go7
-rw-r--r--cmd/podman/containers/top.go2
-rw-r--r--cmd/podman/generate/systemd.go2
-rw-r--r--cmd/podman/images/build.go1
-rw-r--r--cmd/podman/images/inspect.go2
-rw-r--r--cmd/podman/images/list.go2
-rw-r--r--cmd/podman/images/push.go19
-rw-r--r--cmd/podman/images/save.go4
-rw-r--r--cmd/podman/images/trust_show.go1
-rw-r--r--cmd/podman/inspect.go2
-rw-r--r--cmd/podman/main.go2
-rw-r--r--cmd/podman/manifest/create.go2
-rw-r--r--cmd/podman/networks/create.go12
-rw-r--r--cmd/podman/networks/inspect.go2
-rw-r--r--cmd/podman/pods/top.go2
-rw-r--r--cmd/podman/registry/config.go3
-rw-r--r--cmd/podman/registry/config_tunnel.go7
-rw-r--r--cmd/podman/root.go17
-rw-r--r--cmd/podman/system/connection.go209
-rw-r--r--cmd/podman/system/unshare.go2
-rw-r--r--cmd/podman/validate/args.go7
-rw-r--r--completions/bash/podman36
-rwxr-xr-xcontrib/cirrus/logformatter4
-rwxr-xr-xcontrib/cirrus/logformatter.t4
-rw-r--r--docs/source/markdown/podman-auto-update.1.md2
-rw-r--r--docs/source/markdown/podman-system-connection.1.md37
-rw-r--r--docs/source/markdown/podman-system.1.md19
-rw-r--r--docs/source/markdown/podman-untag.1.md8
-rw-r--r--docs/source/markdown/podman.1.md6
-rw-r--r--libpod/container_internal.go20
-rw-r--r--libpod/define/container_inspect.go15
-rw-r--r--libpod/define/errors.go3
-rw-r--r--libpod/image/errors.go2
-rw-r--r--libpod/image/image.go13
-rw-r--r--libpod/networking_linux.go16
-rw-r--r--pkg/api/handlers/libpod/volumes.go2
-rw-r--r--pkg/api/server/register_networks.go10
-rw-r--r--pkg/api/server/register_volumes.go4
-rw-r--r--pkg/bindings/bindings.go43
-rw-r--r--pkg/bindings/connection.go95
-rw-r--r--pkg/domain/entities/container_ps.go2
-rw-r--r--pkg/domain/entities/engine.go3
-rw-r--r--pkg/domain/infra/runtime_abi.go4
-rw-r--r--pkg/domain/infra/runtime_tunnel.go4
-rw-r--r--pkg/terminal/util.go133
-rw-r--r--test/e2e/run_networking_test.go76
-rw-r--r--test/e2e/run_selinux_test.go9
-rw-r--r--test/e2e/run_test.go8
-rw-r--r--test/e2e/run_userns_test.go7
-rw-r--r--test/e2e/untag_test.go76
-rw-r--r--test/system/015-help.bats63
-rw-r--r--test/system/020-tag.bats35
-rw-r--r--test/system/120-load.bats6
-rw-r--r--test/system/helpers.bash14
60 files changed, 787 insertions, 318 deletions
diff --git a/Makefile b/Makefile
index 12e68350e..55b42c431 100644
--- a/Makefile
+++ b/Makefile
@@ -351,7 +351,7 @@ remoteintegration: varlink_generate test-binaries ginkgo-remote
localsystem:
# Wipe existing config, database, and cache: start with clean slate.
$(RM) -rf ${HOME}/.local/share/containers ${HOME}/.config/containers
- if timeout -v 1 true; then PODMAN=./bin/podman bats test/system/; else echo "Skipping $@: 'timeout -v' unavailable'"; fi
+ if timeout -v 1 true; then PODMAN=$(shell pwd)/bin/podman bats test/system/; else echo "Skipping $@: 'timeout -v' unavailable'"; fi
.PHONY: remotesystem
remotesystem:
@@ -378,7 +378,7 @@ remotesystem:
echo "Error: ./bin/podman system service did not come up on $$SOCK_FILE" >&2;\
exit 1;\
fi;\
- env PODMAN="./bin/podman-remote --url $$PODMAN_SOCKET" bats test/system/ ;\
+ env PODMAN="$(shell pwd)/bin/podman-remote --url $$PODMAN_SOCKET" bats test/system/ ;\
rc=$$?;\
kill %1;\
rm -f $$SOCK_FILE;\
diff --git a/cmd/podman/containers/commit.go b/cmd/podman/containers/commit.go
index b3c3d7626..79e2a32a7 100644
--- a/cmd/podman/containers/commit.go
+++ b/cmd/podman/containers/commit.go
@@ -22,7 +22,7 @@ var (
Short: "Create new image based on the changed container",
Long: commitDescription,
RunE: commit,
- Args: cobra.MinimumNArgs(1),
+ Args: cobra.RangeArgs(1, 2),
Example: `podman commit -q --message "committing container to image" reverent_golick image-committed
podman commit -q --author "firstName lastName" reverent_golick image-committed
podman commit -q --pause=false containerID image-committed
@@ -30,7 +30,7 @@ var (
}
containerCommitCommand = &cobra.Command{
- Args: cobra.MinimumNArgs(1),
+ Args: commitCommand.Args,
Use: commitCommand.Use,
Short: commitCommand.Short,
Long: commitCommand.Long,
@@ -82,7 +82,7 @@ func init() {
func commit(cmd *cobra.Command, args []string) error {
container := args[0]
- if len(args) > 1 {
+ if len(args) == 2 {
commitOptions.ImageName = args[1]
}
if !commitOptions.Quiet {
diff --git a/cmd/podman/containers/export.go b/cmd/podman/containers/export.go
index bbb6a6bc9..66e768ccb 100644
--- a/cmd/podman/containers/export.go
+++ b/cmd/podman/containers/export.go
@@ -28,7 +28,7 @@ var (
}
containerExportCommand = &cobra.Command{
- Args: cobra.MinimumNArgs(1),
+ Args: cobra.ExactArgs(1),
Use: exportCommand.Use,
Short: exportCommand.Short,
Long: exportCommand.Long,
diff --git a/cmd/podman/containers/inspect.go b/cmd/podman/containers/inspect.go
index 8556ebe83..e49fcc2e0 100644
--- a/cmd/podman/containers/inspect.go
+++ b/cmd/podman/containers/inspect.go
@@ -10,7 +10,7 @@ import (
var (
// podman container _inspect_
inspectCmd = &cobra.Command{
- Use: "inspect [flags] CONTAINER",
+ Use: "inspect [flags] CONTAINER [CONTAINER...]",
Short: "Display the configuration of a container",
Long: `Displays the low-level information on a container identified by name or ID.`,
RunE: inspectExec,
diff --git a/cmd/podman/containers/mount.go b/cmd/podman/containers/mount.go
index 7f15616de..5c73cceaa 100644
--- a/cmd/podman/containers/mount.go
+++ b/cmd/podman/containers/mount.go
@@ -23,7 +23,7 @@ var (
`
mountCommand = &cobra.Command{
- Use: "mount [flags] [CONTAINER]",
+ Use: "mount [flags] [CONTAINER...]",
Short: "Mount a working container's root filesystem",
Long: mountDescription,
RunE: mount,
diff --git a/cmd/podman/containers/port.go b/cmd/podman/containers/port.go
index d058a6aaf..115adc2a7 100644
--- a/cmd/podman/containers/port.go
+++ b/cmd/podman/containers/port.go
@@ -89,6 +89,9 @@ func port(cmd *cobra.Command, args []string) error {
container = args[0]
}
port := ""
+ if len(args) > 2 {
+ return errors.Errorf("`port` accepts at most 2 arguments")
+ }
if len(args) > 1 && !portOpts.Latest {
port = args[1]
}
diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go
index ffd2054a6..5d3c9263e 100644
--- a/cmd/podman/containers/ps.go
+++ b/cmd/podman/containers/ps.go
@@ -110,7 +110,12 @@ func checkFlags(c *cobra.Command) error {
}
func jsonOut(responses []entities.ListContainer) error {
- b, err := json.MarshalIndent(responses, "", " ")
+ r := make([]entities.ListContainer, 0)
+ for _, con := range responses {
+ con.CreatedAt = units.HumanDuration(time.Since(time.Unix(con.Created, 0))) + " ago"
+ r = append(r, con)
+ }
+ b, err := json.MarshalIndent(r, "", " ")
if err != nil {
return err
}
diff --git a/cmd/podman/containers/top.go b/cmd/podman/containers/top.go
index d2b11ec77..afab12a14 100644
--- a/cmd/podman/containers/top.go
+++ b/cmd/podman/containers/top.go
@@ -25,7 +25,7 @@ var (
topOptions = entities.TopOptions{}
topCommand = &cobra.Command{
- Use: "top [flags] CONTAINER [FORMAT-DESCRIPTORS|ARGS]",
+ Use: "top [flags] CONTAINER [FORMAT-DESCRIPTORS|ARGS...]",
Short: "Display the running processes of a container",
Long: topDescription,
RunE: top,
diff --git a/cmd/podman/generate/systemd.go b/cmd/podman/generate/systemd.go
index e4fdd8690..b4ab0f9df 100644
--- a/cmd/podman/generate/systemd.go
+++ b/cmd/podman/generate/systemd.go
@@ -20,7 +20,7 @@ var (
Short: "Generate systemd units.",
Long: systemdDescription,
RunE: systemd,
- Args: cobra.MinimumNArgs(1),
+ Args: cobra.ExactArgs(1),
Example: `podman generate systemd CTR
podman generate systemd --new --time 10 CTR
podman generate systemd --files --name POD`,
diff --git a/cmd/podman/images/build.go b/cmd/podman/images/build.go
index 23bfcab79..dfde896a1 100644
--- a/cmd/podman/images/build.go
+++ b/cmd/podman/images/build.go
@@ -45,6 +45,7 @@ var (
Long: buildDescription,
TraverseChildren: true,
RunE: build,
+ Args: cobra.MaximumNArgs(1),
Example: `podman build .
podman build --creds=username:password -t imageName -f Containerfile.simple .
podman build --layers --force-rm --tag imageName .`,
diff --git a/cmd/podman/images/inspect.go b/cmd/podman/images/inspect.go
index f6a10ba44..a1a9e91eb 100644
--- a/cmd/podman/images/inspect.go
+++ b/cmd/podman/images/inspect.go
@@ -10,7 +10,7 @@ import (
var (
// Command: podman image _inspect_
inspectCmd = &cobra.Command{
- Use: "inspect [flags] IMAGE",
+ Use: "inspect [flags] IMAGE [IMAGE...]",
Short: "Display the configuration of an image",
Long: `Displays the low-level information of an image identified by name or ID.`,
RunE: inspectExec,
diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go
index b7a8b8911..de7cca40d 100644
--- a/cmd/podman/images/list.go
+++ b/cmd/podman/images/list.go
@@ -32,7 +32,7 @@ type listFlagType struct {
var (
// Command: podman image _list_
listCmd = &cobra.Command{
- Use: "list [FLAGS] [IMAGE]",
+ Use: "list [flags] [IMAGE]",
Aliases: []string{"ls"},
Args: cobra.MaximumNArgs(1),
Short: "List images in local storage",
diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go
index a1614dc7a..7af2d9343 100644
--- a/cmd/podman/images/push.go
+++ b/cmd/podman/images/push.go
@@ -29,10 +29,11 @@ var (
// Command: podman push
pushCmd = &cobra.Command{
- Use: "push [flags] SOURCE DESTINATION",
+ Use: "push [flags] SOURCE [DESTINATION]",
Short: "Push an image to a specified destination",
Long: pushDescription,
RunE: imagePush,
+ Args: cobra.RangeArgs(1, 2),
Example: `podman push imageID docker://registry.example.com/repository:tag
podman push imageID oci-archive:/path/to/layout:image:tag`,
}
@@ -45,6 +46,7 @@ var (
Short: pushCmd.Short,
Long: pushCmd.Long,
RunE: pushCmd.RunE,
+ Args: pushCmd.Args,
Example: `podman image push imageID docker://registry.example.com/repository:tag
podman image push imageID oci-archive:/path/to/layout:image:tag`,
}
@@ -96,19 +98,8 @@ func pushFlags(flags *pflag.FlagSet) {
// imagePush is implement the command for pushing images.
func imagePush(cmd *cobra.Command, args []string) error {
- var source, destination string
- switch len(args) {
- case 1:
- source = args[0]
- destination = args[0]
- case 2:
- source = args[0]
- destination = args[1]
- case 0:
- fallthrough
- default:
- return errors.New("push requires at least one image name, or optionally a second to specify a different destination")
- }
+ source := args[0]
+ destination := args[len(args)-1]
// TLS verification in c/image is controlled via a `types.OptionalBool`
// which allows for distinguishing among set-true, set-false, unspecified
diff --git a/cmd/podman/images/save.go b/cmd/podman/images/save.go
index 56953e41c..9b03c1383 100644
--- a/cmd/podman/images/save.go
+++ b/cmd/podman/images/save.go
@@ -23,8 +23,8 @@ var (
saveDescription = `Save an image to docker-archive or oci-archive on the local machine. Default is docker-archive.`
saveCommand = &cobra.Command{
- Use: "save [flags] IMAGE",
- Short: "Save image to an archive",
+ Use: "save [flags] IMAGE [IMAGE...]",
+ Short: "Save image(s) to an archive",
Long: saveDescription,
RunE: save,
Args: func(cmd *cobra.Command, args []string) error {
diff --git a/cmd/podman/images/trust_show.go b/cmd/podman/images/trust_show.go
index 23ee6c709..dbaa800a4 100644
--- a/cmd/podman/images/trust_show.go
+++ b/cmd/podman/images/trust_show.go
@@ -18,6 +18,7 @@ var (
Short: "Display trust policy for the system",
Long: showTrustDescription,
RunE: showTrust,
+ Args: cobra.MaximumNArgs(1),
Example: "",
}
)
diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go
index a5fdaedc2..6c4607d88 100644
--- a/cmd/podman/inspect.go
+++ b/cmd/podman/inspect.go
@@ -10,7 +10,7 @@ import (
var (
// Command: podman _inspect_ Object_ID
inspectCmd = &cobra.Command{
- Use: "inspect [flags] {CONTAINER_ID | IMAGE_ID}",
+ Use: "inspect [flags] {CONTAINER_ID | IMAGE_ID} [...]",
Short: "Display the configuration of object denoted by ID",
Long: "Displays the low-level information on an object identified by name or ID",
TraverseChildren: true,
diff --git a/cmd/podman/main.go b/cmd/podman/main.go
index 76ec7bc8e..f502e7a67 100644
--- a/cmd/podman/main.go
+++ b/cmd/podman/main.go
@@ -35,7 +35,7 @@ func main() {
_, found := c.Command.Annotations[registry.ParentNSRequired]
if rootless.IsRootless() && found {
c.Command.RunE = func(cmd *cobra.Command, args []string) error {
- return fmt.Errorf("cannot `%s` in rootless mode", cmd.CommandPath())
+ return fmt.Errorf("cannot run command %q in rootless mode", cmd.CommandPath())
}
}
diff --git a/cmd/podman/manifest/create.go b/cmd/podman/manifest/create.go
index 9c0097b90..2ab1fccea 100644
--- a/cmd/podman/manifest/create.go
+++ b/cmd/podman/manifest/create.go
@@ -20,7 +20,7 @@ var (
Example: `podman manifest create mylist:v1.11
podman manifest create mylist:v1.11 arch-specific-image-to-add
podman manifest create --all mylist:v1.11 transport:tagged-image-to-add`,
- Args: cobra.MinimumNArgs(1),
+ Args: cobra.RangeArgs(1, 2),
}
)
diff --git a/cmd/podman/networks/create.go b/cmd/podman/networks/create.go
index 5d28c7140..2d29beddd 100644
--- a/cmd/podman/networks/create.go
+++ b/cmd/podman/networks/create.go
@@ -8,7 +8,6 @@ import (
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/network"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -20,6 +19,7 @@ var (
Short: "network create",
Long: networkCreateDescription,
RunE: networkCreate,
+ Args: cobra.MaximumNArgs(1),
Example: `podman network create podman1`,
Annotations: map[string]string{
registry.ParentNSRequired: "",
@@ -62,14 +62,10 @@ func networkCreate(cmd *cobra.Command, args []string) error {
if err := network.IsSupportedDriver(networkCreateOptions.Driver); err != nil {
return err
}
- if len(args) > 1 {
- return errors.Errorf("only one network can be created at a time")
- }
- if len(args) > 0 && !define.NameRegex.MatchString(args[0]) {
- return define.RegexError
- }
-
if len(args) > 0 {
+ if !define.NameRegex.MatchString(args[0]) {
+ return define.RegexError
+ }
name = args[0]
}
response, err := registry.ContainerEngine().NetworkCreate(registry.Context(), name, networkCreateOptions)
diff --git a/cmd/podman/networks/inspect.go b/cmd/podman/networks/inspect.go
index 1b2e89909..31269e836 100644
--- a/cmd/podman/networks/inspect.go
+++ b/cmd/podman/networks/inspect.go
@@ -16,7 +16,7 @@ import (
var (
networkinspectDescription = `Inspect network`
networkinspectCommand = &cobra.Command{
- Use: "inspect NETWORK [NETWORK...] [flags] ",
+ Use: "inspect [flags] NETWORK [NETWORK...]",
Short: "network inspect",
Long: networkinspectDescription,
RunE: networkInspect,
diff --git a/cmd/podman/pods/top.go b/cmd/podman/pods/top.go
index ba1efb638..8df00f92a 100644
--- a/cmd/podman/pods/top.go
+++ b/cmd/podman/pods/top.go
@@ -22,7 +22,7 @@ var (
topOptions = entities.PodTopOptions{}
topCommand = &cobra.Command{
- Use: "top [flags] POD [FORMAT-DESCRIPTORS|ARGS]",
+ Use: "top [flags] POD [FORMAT-DESCRIPTORS|ARGS...]",
Short: "Display the running processes of containers in a pod",
Long: topDescription,
RunE: top,
diff --git a/cmd/podman/registry/config.go b/cmd/podman/registry/config.go
index 49d5bca74..a67568d73 100644
--- a/cmd/podman/registry/config.go
+++ b/cmd/podman/registry/config.go
@@ -68,7 +68,6 @@ func newPodmanConfig() {
}
}
- // FIXME: for rootless, add flag to get the path to override configuration
cfg, err := config.NewConfig("")
if err != nil {
fmt.Fprint(os.Stderr, "Failed to obtain podman configuration: "+err.Error())
@@ -83,7 +82,7 @@ func newPodmanConfig() {
podmanOptions = entities.PodmanConfig{Config: cfg, EngineMode: mode}
}
-// SetXdgDirs ensures the XDG_RUNTIME_DIR env and XDG_CONFIG_HOME variables are set.
+// setXdgDirs ensures the XDG_RUNTIME_DIR env and XDG_CONFIG_HOME variables are set.
// containers/image uses XDG_RUNTIME_DIR to locate the auth file, XDG_CONFIG_HOME is
// use for the libpod.conf configuration file.
func setXdgDirs() error {
diff --git a/cmd/podman/registry/config_tunnel.go b/cmd/podman/registry/config_tunnel.go
index bb3da947e..4f9f51163 100644
--- a/cmd/podman/registry/config_tunnel.go
+++ b/cmd/podman/registry/config_tunnel.go
@@ -2,6 +2,13 @@
package registry
+import (
+ "os"
+)
+
func init() {
abiSupport = false
+
+ // Enforce that podman-remote == podman --remote
+ os.Args = append(os.Args, "--remote")
}
diff --git a/cmd/podman/root.go b/cmd/podman/root.go
index 4f834e87d..25e53cbee 100644
--- a/cmd/podman/root.go
+++ b/cmd/podman/root.go
@@ -8,6 +8,7 @@ import (
"runtime/pprof"
"strings"
+ "github.com/containers/common/pkg/config"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
@@ -103,13 +104,13 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
// TODO: Remove trace statement in podman V2.1
logrus.Debugf("Called %s.PersistentPreRunE(%s)", cmd.Name(), strings.Join(os.Args, " "))
- cfg := registry.PodmanConfig()
-
// Help is a special case, no need for more setup
if cmd.Name() == "help" {
return nil
}
+ cfg := registry.PodmanConfig()
+
// Prep the engines
if _, err := registry.NewImageEngine(cmd, args); err != nil {
return err
@@ -211,10 +212,14 @@ func loggingHook() {
func rootFlags(opts *entities.PodmanConfig, flags *pflag.FlagSet) {
// V2 flags
flags.BoolVarP(&opts.Remote, "remote", "r", false, "Access remote Podman service (default false)")
- // TODO Read uri from containers.config when available
- flags.StringVar(&opts.URI, "url", registry.DefaultAPIAddress(), "URL to access Podman service (CONTAINER_HOST)")
- flags.StringSliceVar(&opts.Identities, "identity", []string{}, "path to SSH identity file, (CONTAINER_SSHKEY)")
- flags.StringVar(&opts.PassPhrase, "passphrase", "", "passphrase for identity file (not secure, CONTAINER_PASSPHRASE), ssh-agent always supported")
+
+ custom, _ := config.ReadCustomConfig()
+ defaultURI := custom.Engine.RemoteURI
+ if defaultURI == "" {
+ defaultURI = registry.DefaultAPIAddress()
+ }
+ flags.StringVar(&opts.URI, "url", defaultURI, "URL to access Podman service (CONTAINER_HOST)")
+ flags.StringVar(&opts.Identity, "identity", custom.Engine.RemoteIdentity, "path to SSH identity file, (CONTAINER_SSHKEY)")
cfg := opts.Config
flags.StringVar(&cfg.Engine.CgroupManager, "cgroup-manager", cfg.Engine.CgroupManager, "Cgroup manager to use (\"cgroupfs\"|\"systemd\")")
diff --git a/cmd/podman/system/connection.go b/cmd/podman/system/connection.go
new file mode 100644
index 000000000..9f80a454b
--- /dev/null
+++ b/cmd/podman/system/connection.go
@@ -0,0 +1,209 @@
+package system
+
+import (
+ "bytes"
+ "fmt"
+ "net"
+ "net/url"
+ "os"
+ "os/user"
+ "regexp"
+
+ "github.com/containers/common/pkg/config"
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/terminal"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+ "golang.org/x/crypto/ssh"
+ "golang.org/x/crypto/ssh/agent"
+)
+
+const schemaPattern = "^[A-Za-z][A-Za-z0-9+.-]*:"
+
+var (
+ // Skip creating engines since this command will obtain connection information to engine
+ noOp = func(cmd *cobra.Command, args []string) error {
+ return nil
+ }
+ connectionCmd = &cobra.Command{
+ Use: "connection [flags] DESTINATION",
+ Args: cobra.ExactArgs(1),
+ Long: `Store ssh destination information in podman configuration.
+ "destination" is of the form [user@]hostname or
+ an URI of the form ssh://[user@]hostname[:port]
+`,
+ Short: "Record remote ssh destination",
+ PersistentPreRunE: noOp,
+ PersistentPostRunE: noOp,
+ TraverseChildren: false,
+ RunE: connection,
+ Example: `podman system connection server.fubar.com
+ podman system connection --identity ~/.ssh/dev_rsa ssh://root@server.fubar.com:2222
+ podman system connection --identity ~/.ssh/dev_rsa --port 22 root@server.fubar.com`,
+ }
+
+ cOpts = struct {
+ Identity string
+ Port int
+ UDSPath string
+ }{}
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: connectionCmd,
+ Parent: systemCmd,
+ })
+
+ flags := connectionCmd.Flags()
+ flags.StringVar(&cOpts.Identity, "identity", "", "path to ssh identity file")
+ flags.IntVarP(&cOpts.Port, "port", "p", 22, "port number for destination")
+ flags.StringVar(&cOpts.UDSPath, "socket-path", "", "path to podman socket on remote host. (default '/run/podman/podman.sock' or '/run/user/{uid}/podman/podman.sock)")
+}
+
+func connection(cmd *cobra.Command, args []string) error {
+ // Default to ssh: schema if none given
+ dest := []byte(args[0])
+ if match, err := regexp.Match(schemaPattern, dest); err != nil {
+ return errors.Wrapf(err, "internal regex error %q", schemaPattern)
+ } else if !match {
+ dest = append([]byte("ssh://"), dest...)
+ }
+
+ uri, err := url.Parse(string(dest))
+ if err != nil {
+ return errors.Wrapf(err, "failed to parse %q", string(dest))
+ }
+
+ if uri.User.Username() == "" {
+ if uri.User, err = getUserInfo(uri); err != nil {
+ return err
+ }
+ }
+
+ if cmd.Flag("socket-path").Changed {
+ uri.Path = cmd.Flag("socket-path").Value.String()
+ }
+
+ if cmd.Flag("port").Changed {
+ uri.Host = net.JoinHostPort(uri.Hostname(), cmd.Flag("port").Value.String())
+ }
+
+ if uri.Port() == "" {
+ uri.Host = net.JoinHostPort(uri.Hostname(), cmd.Flag("port").DefValue)
+ }
+
+ if uri.Path == "" {
+ if uri.Path, err = getUDS(cmd, uri); err != nil {
+ return errors.Wrapf(err, "failed to connect to %q", uri.String())
+ }
+ }
+
+ custom, err := config.ReadCustomConfig()
+ if err != nil {
+ return err
+ }
+
+ if cmd.Flag("identity").Changed {
+ custom.Engine.RemoteIdentity = cOpts.Identity
+ }
+
+ custom.Engine.RemoteURI = uri.String()
+ return custom.Write()
+}
+
+func getUserInfo(uri *url.URL) (*url.Userinfo, error) {
+ var (
+ usr *user.User
+ err error
+ )
+ if u, found := os.LookupEnv("_CONTAINERS_ROOTLESS_UID"); found {
+ usr, err = user.LookupId(u)
+ if err != nil {
+ return nil, errors.Wrapf(err, "failed to find user %q", u)
+ }
+ } else {
+ usr, err = user.Current()
+ if err != nil {
+ return nil, errors.Wrapf(err, "failed to obtain current user")
+ }
+ }
+
+ pw, set := uri.User.Password()
+ if set {
+ return url.UserPassword(usr.Username, pw), nil
+ }
+ return url.User(usr.Username), nil
+}
+
+func getUDS(cmd *cobra.Command, uri *url.URL) (string, error) {
+ var authMethods []ssh.AuthMethod
+ passwd, set := uri.User.Password()
+ if set {
+ authMethods = append(authMethods, ssh.Password(passwd))
+ }
+
+ ident := cmd.Flag("identity")
+ if ident.Changed {
+ auth, err := terminal.PublicKey(ident.Value.String(), []byte(passwd))
+ if err != nil {
+ return "", errors.Wrapf(err, "Failed to read identity %q", ident.Value.String())
+ }
+ authMethods = append(authMethods, auth)
+ }
+
+ if sock, found := os.LookupEnv("SSH_AUTH_SOCK"); found {
+ logrus.Debugf("Found SSH_AUTH_SOCK %q, ssh-agent signer enabled", sock)
+
+ c, err := net.Dial("unix", sock)
+ if err != nil {
+ return "", err
+ }
+ a := agent.NewClient(c)
+ authMethods = append(authMethods, ssh.PublicKeysCallback(a.Signers))
+ }
+
+ config := &ssh.ClientConfig{
+ User: uri.User.Username(),
+ Auth: authMethods,
+ HostKeyCallback: ssh.InsecureIgnoreHostKey(),
+ }
+ dial, err := ssh.Dial("tcp", uri.Host, config)
+ if err != nil {
+ return "", errors.Wrapf(err, "failed to connect to %q", uri.Host)
+ }
+ defer dial.Close()
+
+ session, err := dial.NewSession()
+ if err != nil {
+ return "", errors.Wrapf(err, "failed to create new ssh session on %q", uri.Host)
+ }
+ defer session.Close()
+
+ // Override podman binary for testing etc
+ podman := "podman"
+ if v, found := os.LookupEnv("PODMAN_BINARY"); found {
+ podman = v
+ }
+ run := podman + " info --format=json"
+
+ var buffer bytes.Buffer
+ session.Stdout = &buffer
+ if err := session.Run(run); err != nil {
+ return "", errors.Wrapf(err, "failed to run %q", run)
+ }
+
+ var info define.Info
+ if err := json.Unmarshal(buffer.Bytes(), &info); err != nil {
+ return "", errors.Wrapf(err, "failed to parse 'podman info' results")
+ }
+
+ if info.Host.RemoteSocket == nil || len(info.Host.RemoteSocket.Path) == 0 {
+ return "", fmt.Errorf("remote podman %q failed to report its UDS socket", uri.Host)
+ }
+ return info.Host.RemoteSocket.Path, nil
+}
diff --git a/cmd/podman/system/unshare.go b/cmd/podman/system/unshare.go
index 7db5d36d2..e5d41e06d 100644
--- a/cmd/podman/system/unshare.go
+++ b/cmd/podman/system/unshare.go
@@ -13,7 +13,7 @@ import (
var (
unshareDescription = "Runs a command in a modified user namespace."
unshareCommand = &cobra.Command{
- Use: "unshare [flags] [COMMAND [ARG]]",
+ Use: "unshare [flags] [COMMAND [ARG ...]]",
Short: "Run a command in a modified user namespace",
Long: unshareDescription,
RunE: unshare,
diff --git a/cmd/podman/validate/args.go b/cmd/podman/validate/args.go
index 69240798f..d170447ee 100644
--- a/cmd/podman/validate/args.go
+++ b/cmd/podman/validate/args.go
@@ -25,8 +25,11 @@ func SubCommandExists(cmd *cobra.Command, args []string) error {
// IDOrLatestArgs used to validate a nameOrId was provided or the "--latest" flag
func IDOrLatestArgs(cmd *cobra.Command, args []string) error {
- if len(args) > 1 || (len(args) == 0 && !cmd.Flag("latest").Changed) {
- return fmt.Errorf("`%s` requires a name, id or the \"--latest\" flag", cmd.CommandPath())
+ if len(args) > 1 {
+ return fmt.Errorf("`%s` accepts at most one argument", cmd.CommandPath())
+ }
+ if len(args) == 0 && !cmd.Flag("latest").Changed {
+ return fmt.Errorf("`%s` requires a name, id, or the \"--latest\" flag", cmd.CommandPath())
}
return nil
}
diff --git a/completions/bash/podman b/completions/bash/podman
index 595739abf..c0d9560ed 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -1018,14 +1018,15 @@ _podman_network_create() {
;;
esac
}
+
_podman_network_inspect() {
local options_with_args="
+ --format
+ -f
"
local boolean_options="
--help
-h
- --format
- -f
"
_complete_ "$options_with_args" "$boolean_options"
@@ -1038,15 +1039,15 @@ _podman_network_inspect() {
_podman_network_ls() {
local options_with_args="
+ --format
+ -f
+ --filter
"
local boolean_options="
--help
-h
--quiet
-q
- --format
- -f
- -- filter
"
_complete_ "$options_with_args" "$boolean_options"
@@ -1571,6 +1572,11 @@ _podman_image_tag() {
_podman_tag
}
+
+_podman_image_untag() {
+ _podman_untag
+}
+
_podman_image() {
local boolean_options="
--help
@@ -1592,6 +1598,7 @@ _podman_image() {
sign
tag
trust
+ untag
"
local aliases="
list
@@ -2459,6 +2466,23 @@ _podman_tag() {
esac
}
+_podman_untag() {
+ local options_with_args="
+ "
+ local boolean_options="
+ --help
+ -h
+ "
+ case "$cur" in
+ -*)
+ COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
+ ;;
+ *)
+ __podman_complete_images
+ ;;
+ esac
+}
+
__podman_top_descriptors() {
podman top --list-descriptors
}
@@ -3565,6 +3589,7 @@ _podman_podman() {
logs
manifest
mount
+ network
pause
pod
port
@@ -3586,6 +3611,7 @@ _podman_podman() {
umount
unmount
unpause
+ untag
varlink
version
volume
diff --git a/contrib/cirrus/logformatter b/contrib/cirrus/logformatter
index 4bfe7b97f..60c1e5985 100755
--- a/contrib/cirrus/logformatter
+++ b/contrib/cirrus/logformatter
@@ -231,6 +231,10 @@ END_HTML
if ($looks_like_bats) {
my $css;
+ # Readability: /long/path/to/podman -> podman (hover for full path)
+ $line =~ s{^(#\s+(#|\$)\s+)(\S+/)(podman\S*)\s}
+ {$1<span title="$3$4">$4</span> };
+
if ($line =~ /^ok\s.*\s# skip/) { $css = 'skipped' }
elsif ($line =~ /^ok\s/) { $css = 'passed' }
elsif ($line =~ /^not\s+ok\s/) { $css = 'failed' }
diff --git a/contrib/cirrus/logformatter.t b/contrib/cirrus/logformatter.t
index 79c4563c2..d2193cc6c 100755
--- a/contrib/cirrus/logformatter.t
+++ b/contrib/cirrus/logformatter.t
@@ -88,12 +88,16 @@ __END__
ok 1 hi
ok 2 bye # skip no reason
not ok 3 fail
+# $ /path/to/podman foo -bar
+# #| FAIL: exit code is 123; expected 321
ok 4 blah
>>>
1..4
<span class='bats-passed'><a name='t--00001'>ok 1 hi</a></span>
<span class='bats-skipped'><a name='t--00002'>ok 2 bye # skip no reason</a></span>
<span class='bats-failed'><a name='t--00003'>not ok 3 fail</a></span>
+<span class='bats-log'># $ <span title="/path/to/podman">podman</span> foo -bar</span>
+<span class='bats-log-esm'># #| FAIL: exit code is 123; expected 321</span>
<span class='bats-passed'><a name='t--00004'>ok 4 blah</a></span>
<hr/><span class='bats-summary'>Summary: <span class='bats-passed'>2 Passed</span>, <span class='bats-failed'>1 Failed</span>, <span class='bats-skipped'>1 Skipped</span>. Total tests: 4</span>
diff --git a/docs/source/markdown/podman-auto-update.1.md b/docs/source/markdown/podman-auto-update.1.md
index 435a767c1..90e581e42 100644
--- a/docs/source/markdown/podman-auto-update.1.md
+++ b/docs/source/markdown/podman-auto-update.1.md
@@ -38,7 +38,7 @@ environment variable. `export REGISTRY_AUTH_FILE=path`
```
# Start a container
$ podman run --label "io.containers.autoupdate=image" \
- --label "io.containers.autoupdate.autfile=/some/authfile.json" \
+ --label "io.containers.autoupdate.authfile=/some/authfile.json" \
-d busybox:latest top
bc219740a210455fa27deacc96d50a9e20516492f1417507c13ce1533dbdcd9d
diff --git a/docs/source/markdown/podman-system-connection.1.md b/docs/source/markdown/podman-system-connection.1.md
new file mode 100644
index 000000000..ed73980d6
--- /dev/null
+++ b/docs/source/markdown/podman-system-connection.1.md
@@ -0,0 +1,37 @@
+% podman-system-connection(1)
+
+## NAME
+podman\-system\-connection - Record ssh destination for remote podman service
+
+## SYNOPSIS
+**podman system connection** [*options*] [*ssh destination*]
+
+## DESCRIPTION
+Record ssh destination for remote podman service(s). The ssh destination is given as one of:
+ - [user@]hostname[:port]
+ - ssh://[user@]hostname[:port]
+
+The user will be prompted for the remote ssh login password or key file pass phrase as required. `ssh-agent` is supported if it is running.
+
+## OPTIONS
+
+**-p**, **--port**=*port*
+
+Port for ssh destination. The default value is `22`.
+
+**--socket-path**=*path*
+
+Path to podman service unix domain socket on the ssh destination host
+
+## EXAMPLE
+```
+$ podman system connection podman.fubar.com
+
+$ podman system connection --identity ~/.ssh/dev_rsa ssh://root@server.fubar.com:2222
+
+```
+## SEE ALSO
+podman-system(1) , containers.conf(5) , connections.conf(5)
+
+## HISTORY
+June 2020, Originally compiled by Jhon Honce (jhonce at redhat dot com)
diff --git a/docs/source/markdown/podman-system.1.md b/docs/source/markdown/podman-system.1.md
index 5f163c6f0..1f19fd0b6 100644
--- a/docs/source/markdown/podman-system.1.md
+++ b/docs/source/markdown/podman-system.1.md
@@ -11,15 +11,16 @@ The system command allows you to manage the podman systems
## COMMANDS
-| Command | Man Page | Description |
-| ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
-| df | [podman-system-df(1)](podman-system-df.1.md) | Show podman disk usage. |
-| info | [podman-system-info(1)](podman-info.1.md) | Displays Podman related system information. |
-| migrate | [podman-system-migrate(1)](podman-system-migrate.1.md)| Migrate existing containers to a new podman version. |
-| prune | [podman-system-prune(1)](podman-system-prune.1.md) | Remove all unused container, image and volume data. |
-| renumber | [podman-system-renumber(1)](podman-system-renumber.1.md)| Migrate lock numbers to handle a change in maximum number of locks. |
-| reset | [podman-system-reset(1)](podman-system-reset.1.md) | Reset storage back to initial state. |
-| service | [podman-service(1)](podman-system-service.1.md) | Run an API service |
+| Command | Man Page | Description |
+| ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
+| df | [podman-system-df(1)](podman-system-df.1.md) | Show podman disk usage. |
+| connection | [podman-system-connection(1)](podman-system-connection.1.md) | Record ssh destination for remote podman service. |
+| info | [podman-system-info(1)](podman-info.1.md) | Displays Podman related system information. |
+| migrate | [podman-system-migrate(1)](podman-system-migrate.1.md) | Migrate existing containers to a new podman version. |
+| prune | [podman-system-prune(1)](podman-system-prune.1.md) | Remove all unused container, image and volume data. |
+| renumber | [podman-system-renumber(1)](podman-system-renumber.1.md) | Migrate lock numbers to handle a change in maximum number of locks. |
+| reset | [podman-system-reset(1)](podman-system-reset.1.md) | Reset storage back to initial state. |
+| service | [podman-service(1)](podman-system-service.1.md) | Run an API service |
## SEE ALSO
diff --git a/docs/source/markdown/podman-untag.1.md b/docs/source/markdown/podman-untag.1.md
index 3a54283d6..c83a0544c 100644
--- a/docs/source/markdown/podman-untag.1.md
+++ b/docs/source/markdown/podman-untag.1.md
@@ -4,14 +4,12 @@
podman\-untag - Removes one or more names from a locally-stored image
## SYNOPSIS
-**podman untag** *image*[:*tag*] [*target-names*[:*tag*]] [*options*]
+**podman untag** [*options*] *image* [*name*[:*tag*]...]
-**podman image untag** *image*[:*tag*] [target-names[:*tag*]] [*options*]
+**podman image untag** [*options*] *image* [*name*[:*tag*]...]
## DESCRIPTION
-Removes one or all names of an image. A name refers to the entire image name,
-including the optional *tag* after the `:`. If no target image names are
-specified, `untag` will remove all tags for the image at once.
+Remove one or more names from an image in the local storage. The image can be referred to by ID or reference. If a no name is specified, all names are removed the image. If a specified name is a short name and does not include a registry `localhost/` will be prefixed (e.g., `fedora` -> `localhost/fedora`). If a specified name does not include a tag `:latest` will be appended (e.g., `localhost/fedora` -> `localhost/fedora:latest`).
## OPTIONS
diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md
index 2f338452c..ce02ef3a7 100644
--- a/docs/source/markdown/podman.1.md
+++ b/docs/source/markdown/podman.1.md
@@ -59,10 +59,10 @@ Podman and libpod currently support an additional `precreate` state which is cal
**WARNING**: the `precreate` hook lets you do powerful things, such as adding additional mounts to the runtime configuration. That power also makes it easy to break things. Before reporting libpod errors, try running your container with `precreate` hooks disabled to see if the problem is due to one of your hooks.
**--identity**=*path*
-Path to SSH identity file
-**--passphrase**=*secret*
-pass phrase for SSH identity file
+Path to ssh identity file. If the identity file has been encrypted, podman prompts the user for the passphrase.
+If no identity file is provided and no user is given, podman defaults to the user running the podman command.
+Podman prompts for the login password on the remote server.
**--log-level**=*level*
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 73e0b2118..db64f5eeb 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -22,6 +22,7 @@ import (
"github.com/containers/libpod/pkg/selinux"
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
+ "github.com/containers/storage/pkg/idtools"
"github.com/containers/storage/pkg/mount"
securejoin "github.com/cyphar/filepath-securejoin"
spec "github.com/opencontainers/runtime-spec/specs-go"
@@ -360,6 +361,25 @@ func (c *Container) setupStorageMapping(dest, from *storage.IDMappingOptions) {
}
dest.AutoUserNsOpts.InitialSize = initialSize + 1
}
+ } else if c.config.Spec.Linux != nil {
+ dest.UIDMap = nil
+ for _, r := range c.config.Spec.Linux.UIDMappings {
+ u := idtools.IDMap{
+ ContainerID: int(r.ContainerID),
+ HostID: int(r.HostID),
+ Size: int(r.Size),
+ }
+ dest.UIDMap = append(dest.UIDMap, u)
+ }
+ dest.GIDMap = nil
+ for _, r := range c.config.Spec.Linux.GIDMappings {
+ g := idtools.IDMap{
+ ContainerID: int(r.ContainerID),
+ HostID: int(r.HostID),
+ Size: int(r.Size),
+ }
+ dest.GIDMap = append(dest.GIDMap, g)
+ }
}
}
diff --git a/libpod/define/container_inspect.go b/libpod/define/container_inspect.go
index 27ada8706..3fbeb8f0b 100644
--- a/libpod/define/container_inspect.go
+++ b/libpod/define/container_inspect.go
@@ -5,7 +5,6 @@ import (
"github.com/containers/image/v5/manifest"
"github.com/containers/libpod/libpod/driver"
- "github.com/cri-o/ocicni/pkg/ocicni"
)
// InspectContainerConfig holds further data about how a container was initially
@@ -571,13 +570,13 @@ type InspectAdditionalNetwork struct {
type InspectNetworkSettings struct {
InspectBasicNetworkConfig
- Bridge string `json:"Bridge"`
- SandboxID string `json:"SandboxID"`
- HairpinMode bool `json:"HairpinMode"`
- LinkLocalIPv6Address string `json:"LinkLocalIPv6Address"`
- LinkLocalIPv6PrefixLen int `json:"LinkLocalIPv6PrefixLen"`
- Ports []ocicni.PortMapping `json:"Ports"`
- SandboxKey string `json:"SandboxKey"`
+ Bridge string `json:"Bridge"`
+ SandboxID string `json:"SandboxID"`
+ HairpinMode bool `json:"HairpinMode"`
+ LinkLocalIPv6Address string `json:"LinkLocalIPv6Address"`
+ LinkLocalIPv6PrefixLen int `json:"LinkLocalIPv6PrefixLen"`
+ Ports map[string][]InspectHostPort `json:"Ports"`
+ SandboxKey string `json:"SandboxKey"`
// Networks contains information on non-default CNI networks this
// container has joined.
// It is a map of network name to network information.
diff --git a/libpod/define/errors.go b/libpod/define/errors.go
index e0c9811fe..98dc603d1 100644
--- a/libpod/define/errors.go
+++ b/libpod/define/errors.go
@@ -17,6 +17,9 @@ var (
// ErrNoSuchImage indicates the requested image does not exist
ErrNoSuchImage = image.ErrNoSuchImage
+ // ErrNoSuchTag indicates the requested image tag does not exist
+ ErrNoSuchTag = image.ErrNoSuchTag
+
// ErrNoSuchVolume indicates the requested volume does not exist
ErrNoSuchVolume = errors.New("no such volume")
diff --git a/libpod/image/errors.go b/libpod/image/errors.go
index 4088946cb..ddbf7be4b 100644
--- a/libpod/image/errors.go
+++ b/libpod/image/errors.go
@@ -12,4 +12,6 @@ var (
ErrNoSuchPod = errors.New("no such pod")
// ErrNoSuchImage indicates the requested image does not exist
ErrNoSuchImage = errors.New("no such image")
+ // ErrNoSuchTag indicates the requested image tag does not exist
+ ErrNoSuchTag = errors.New("no such tag")
)
diff --git a/libpod/image/image.go b/libpod/image/image.go
index d81f7e911..83e7467e9 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -559,15 +559,24 @@ func (i *Image) TagImage(tag string) error {
return nil
}
-// UntagImage removes a tag from the given image
+// UntagImage removes the specified tag from the image.
+// If the tag does not exist, ErrNoSuchTag is returned.
func (i *Image) UntagImage(tag string) error {
if err := i.reloadImage(); err != nil {
return err
}
+
+ // Normalize the tag as we do with TagImage.
+ ref, err := NormalizedTag(tag)
+ if err != nil {
+ return err
+ }
+ tag = ref.String()
+
var newTags []string
tags := i.Names()
if !util.StringInSlice(tag, tags) {
- return nil
+ return errors.Wrapf(ErrNoSuchTag, "%q", tag)
}
for _, t := range tags {
if tag != t {
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index 0c9d28701..f53573645 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -587,10 +587,20 @@ func getContainerNetIO(ctr *Container) (*netlink.LinkStatistics, error) {
// network.
func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, error) {
settings := new(define.InspectNetworkSettings)
- settings.Ports = []ocicni.PortMapping{}
+ settings.Ports = make(map[string][]define.InspectHostPort)
if c.config.PortMappings != nil {
- // TODO: This may not be safe.
- settings.Ports = c.config.PortMappings
+ for _, port := range c.config.PortMappings {
+ key := fmt.Sprintf("%d/%s", port.ContainerPort, port.Protocol)
+ mapping := settings.Ports[key]
+ if mapping == nil {
+ mapping = []define.InspectHostPort{}
+ }
+ mapping = append(mapping, define.InspectHostPort{
+ HostIP: port.HostIP,
+ HostPort: fmt.Sprintf("%d", port.HostPort),
+ })
+ settings.Ports[key] = mapping
+ }
}
// We can't do more if the network is down.
diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go
index ea035fc4d..4b3b5430b 100644
--- a/pkg/api/handlers/libpod/volumes.go
+++ b/pkg/api/handlers/libpod/volumes.go
@@ -73,7 +73,7 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) {
UID: config.UID,
GID: config.GID,
}
- utils.WriteResponse(w, http.StatusOK, volResponse)
+ utils.WriteResponse(w, http.StatusCreated, volResponse)
}
func InspectVolume(w http.ResponseWriter, r *http.Request) {
diff --git a/pkg/api/server/register_networks.go b/pkg/api/server/register_networks.go
index 2c60b2b27..3ea16f81a 100644
--- a/pkg/api/server/register_networks.go
+++ b/pkg/api/server/register_networks.go
@@ -32,7 +32,7 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/networks/{name}"), s.APIHandler(compat.RemoveNetwork)).Methods(http.MethodDelete)
r.HandleFunc("/networks/{name}", s.APIHandler(compat.RemoveNetwork)).Methods(http.MethodDelete)
- // swagger:operation GET /networks/{name}/json compat compatInspectNetwork
+ // swagger:operation GET /networks/{name} compat compatInspectNetwork
// ---
// tags:
// - networks (compat)
@@ -53,9 +53,9 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// $ref: "#/responses/NoSuchNetwork"
// 500:
// $ref: "#/responses/InternalError"
- r.HandleFunc(VersionedPath("/networks/{name}/json"), s.APIHandler(compat.InspectNetwork)).Methods(http.MethodGet)
- r.HandleFunc("/networks/{name}/json", s.APIHandler(compat.InspectNetwork)).Methods(http.MethodGet)
- // swagger:operation GET /networks/json compat compatListNetwork
+ r.HandleFunc(VersionedPath("/networks/{name}"), s.APIHandler(compat.InspectNetwork)).Methods(http.MethodGet)
+ r.HandleFunc("/networks/{name}", s.APIHandler(compat.InspectNetwork)).Methods(http.MethodGet)
+ // swagger:operation GET /networks compat compatListNetwork
// ---
// tags:
// - networks (compat)
@@ -68,7 +68,7 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// $ref: "#/responses/CompatNetworkList"
// 500:
// $ref: "#/responses/InternalError"
- r.HandleFunc(VersionedPath("/networks/json"), s.APIHandler(compat.ListNetworks)).Methods(http.MethodGet)
+ r.HandleFunc(VersionedPath("/networks"), s.APIHandler(compat.ListNetworks)).Methods(http.MethodGet)
r.HandleFunc("/networks", s.APIHandler(compat.ListNetworks)).Methods(http.MethodGet)
// swagger:operation POST /networks/create compat compatCreateNetwork
// ---
diff --git a/pkg/api/server/register_volumes.go b/pkg/api/server/register_volumes.go
index 93b972b6b..1d5abd830 100644
--- a/pkg/api/server/register_volumes.go
+++ b/pkg/api/server/register_volumes.go
@@ -28,7 +28,7 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error {
// swagger:operation GET /libpod/volumes/json volumes listVolumes
// ---
// summary: List volumes
- // description: Returns a list of networks
+ // description: Returns a list of volumes
// produces:
// - application/json
// parameters:
@@ -36,7 +36,7 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error {
// name: filters
// type: string
// description: |
- // JSON encoded value of the filters (a map[string][]string) to process on the networks list. Available filters:
+ // JSON encoded value of the filters (a map[string][]string) to process on the volumes list. Available filters:
// - driver=<volume-driver-name> Matches volumes based on their driver.
// - label=<key> or label=<key>:<value> Matches volumes based on the presence of a label alone or a label and a value.
// - name=<volume-name> Matches all of volume name.
diff --git a/pkg/bindings/bindings.go b/pkg/bindings/bindings.go
index 94f7a45d0..ae5610b0f 100644
--- a/pkg/bindings/bindings.go
+++ b/pkg/bindings/bindings.go
@@ -8,13 +8,7 @@
package bindings
import (
- "errors"
- "fmt"
- "io"
- "os"
-
"github.com/blang/semver"
- "golang.org/x/crypto/ssh/terminal"
)
var (
@@ -30,40 +24,3 @@ var (
// APIVersion - podman will fail to run if this value is wrong
APIVersion = semver.MustParse("1.0.0")
)
-
-// readPassword prompts for a secret and returns value input by user from stdin
-// Unlike terminal.ReadPassword(), $(echo $SECRET | podman...) is supported.
-// Additionally, all input after `<secret>/n` is queued to podman command.
-func readPassword(prompt string) (pw []byte, err error) {
- fd := int(os.Stdin.Fd())
- if terminal.IsTerminal(fd) {
- fmt.Fprint(os.Stderr, prompt)
- pw, err = terminal.ReadPassword(fd)
- fmt.Fprintln(os.Stderr)
- return
- }
-
- var b [1]byte
- for {
- n, err := os.Stdin.Read(b[:])
- // terminal.ReadPassword discards any '\r', so we do the same
- if n > 0 && b[0] != '\r' {
- if b[0] == '\n' {
- return pw, nil
- }
- pw = append(pw, b[0])
- // limit size, so that a wrong input won't fill up the memory
- if len(pw) > 1024 {
- err = errors.New("password too long, 1024 byte limit")
- }
- }
- if err != nil {
- // terminal.ReadPassword accepts EOF-terminated passwords
- // if non-empty, so we do the same
- if err == io.EOF && len(pw) > 0 {
- err = nil
- }
- return pw, err
- }
- }
-}
diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go
index a9c61e5ae..c02d55e31 100644
--- a/pkg/bindings/connection.go
+++ b/pkg/bindings/connection.go
@@ -1,28 +1,24 @@
package bindings
import (
- "bufio"
"context"
"fmt"
"io"
- "io/ioutil"
"net"
"net/http"
"net/url"
"os"
- "path/filepath"
"strconv"
"strings"
- "sync"
"time"
"github.com/blang/semver"
+ "github.com/containers/libpod/pkg/terminal"
jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
- "k8s.io/client-go/util/homedir"
)
var (
@@ -31,8 +27,6 @@ var (
Host: "d",
Path: "/v" + APIVersion.String() + "/libpod",
}
- passPhrase []byte
- phraseSync sync.Once
)
type APIResponse struct {
@@ -77,7 +71,7 @@ func NewConnection(ctx context.Context, uri string) (context.Context, error) {
// For example tcp://localhost:<port>
// or unix:///run/podman/podman.sock
// or ssh://<user>@<host>[:port]/run/podman/podman.sock?secure=True
-func NewConnectionWithIdentity(ctx context.Context, uri string, passPhrase string, identities ...string) (context.Context, error) {
+func NewConnectionWithIdentity(ctx context.Context, uri string, identity string) (context.Context, error) {
var (
err error
secure bool
@@ -86,11 +80,12 @@ func NewConnectionWithIdentity(ctx context.Context, uri string, passPhrase strin
uri = v
}
- if v, found := os.LookupEnv("CONTAINER_SSHKEY"); found && len(identities) == 0 {
- identities = append(identities, v)
+ if v, found := os.LookupEnv("CONTAINER_SSHKEY"); found && len(identity) == 0 {
+ identity = v
}
- if v, found := os.LookupEnv("CONTAINER_PASSPHRASE"); found && passPhrase == "" {
+ passPhrase := ""
+ if v, found := os.LookupEnv("CONTAINER_PASSPHRASE"); found {
passPhrase = v
}
@@ -98,7 +93,6 @@ func NewConnectionWithIdentity(ctx context.Context, uri string, passPhrase strin
if err != nil {
return nil, errors.Wrapf(err, "Value of CONTAINER_HOST is not a valid url: %s", uri)
}
- // TODO Fill in missing defaults for _url...
// Now we setup the http Client to use the connection above
var connection Connection
@@ -108,7 +102,7 @@ func NewConnectionWithIdentity(ctx context.Context, uri string, passPhrase strin
if err != nil {
secure = false
}
- connection, err = sshClient(_url, secure, passPhrase, identities...)
+ connection, err = sshClient(_url, secure, passPhrase, identity)
case "unix":
if !strings.HasPrefix(uri, "unix:///") {
// autofix unix://path_element vs unix:///path_element
@@ -122,7 +116,7 @@ func NewConnectionWithIdentity(ctx context.Context, uri string, passPhrase strin
}
connection = tcpClient(_url)
default:
- return nil, errors.Errorf("'%s' is not a supported schema", _url.Scheme)
+ return nil, errors.Errorf("unable to create connection. %q is not a supported schema", _url.Scheme)
}
if err != nil {
return nil, errors.Wrapf(err, "Failed to create %sClient", _url.Scheme)
@@ -185,14 +179,15 @@ func pingNewConnection(ctx context.Context) error {
return errors.Errorf("ping response was %q", response.StatusCode)
}
-func sshClient(_url *url.URL, secure bool, passPhrase string, identities ...string) (Connection, error) {
+func sshClient(_url *url.URL, secure bool, passPhrase string, identity string) (Connection, error) {
authMethods := []ssh.AuthMethod{}
- for _, i := range identities {
- auth, err := publicKey(i, []byte(passPhrase))
+
+ if len(identity) > 0 {
+ auth, err := terminal.PublicKey(identity, []byte(passPhrase))
if err != nil {
- fmt.Fprint(os.Stderr, errors.Wrapf(err, "failed to parse identity %q", i).Error()+"\n")
- continue
+ return Connection{}, errors.Wrapf(err, "failed to parse identity %q", identity)
}
+ logrus.Debugf("public key signer enabled for identity %q", identity)
authMethods = append(authMethods, auth)
}
@@ -213,7 +208,7 @@ func sshClient(_url *url.URL, secure bool, passPhrase string, identities ...stri
callback := ssh.InsecureIgnoreHostKey()
if secure {
- key := hostKey(_url.Hostname())
+ key := terminal.HostKey(_url.Hostname())
if key != nil {
callback = ssh.FixedHostKey(key)
}
@@ -339,63 +334,3 @@ func (h *APIResponse) IsClientError() bool {
func (h *APIResponse) IsServerError() bool {
return h.Response.StatusCode/100 == 5
}
-
-func publicKey(path string, passphrase []byte) (ssh.AuthMethod, error) {
- key, err := ioutil.ReadFile(path)
- if err != nil {
- return nil, err
- }
-
- signer, err := ssh.ParsePrivateKey(key)
- if err != nil {
- if _, ok := err.(*ssh.PassphraseMissingError); !ok {
- return nil, err
- }
- if len(passphrase) == 0 {
- phraseSync.Do(promptPassphrase)
- passphrase = passPhrase
- }
- signer, err = ssh.ParsePrivateKeyWithPassphrase(key, passphrase)
- if err != nil {
- return nil, err
- }
- }
- return ssh.PublicKeys(signer), nil
-}
-
-func promptPassphrase() {
- phrase, err := readPassword("Key Passphrase: ")
- if err != nil {
- passPhrase = []byte{}
- return
- }
- passPhrase = phrase
-}
-
-func hostKey(host string) ssh.PublicKey {
- // parse OpenSSH known_hosts file
- // ssh or use ssh-keyscan to get initial key
- knownHosts := filepath.Join(homedir.HomeDir(), ".ssh", "known_hosts")
- fd, err := os.Open(knownHosts)
- if err != nil {
- logrus.Error(err)
- return nil
- }
-
- scanner := bufio.NewScanner(fd)
- for scanner.Scan() {
- _, hosts, key, _, _, err := ssh.ParseKnownHosts(scanner.Bytes())
- if err != nil {
- logrus.Errorf("Failed to parse known_hosts: %s", scanner.Text())
- continue
- }
-
- for _, h := range hosts {
- if h == host {
- return key
- }
- }
- }
-
- return nil
-}
diff --git a/pkg/domain/entities/container_ps.go b/pkg/domain/entities/container_ps.go
index c5e11f188..05627c4b3 100644
--- a/pkg/domain/entities/container_ps.go
+++ b/pkg/domain/entities/container_ps.go
@@ -15,6 +15,8 @@ type ListContainer struct {
Command []string
// Container creation time
Created int64
+ // Human readable container creation time.
+ CreatedAt string
// If container has exited/stopped
Exited bool
// Time container exited
diff --git a/pkg/domain/entities/engine.go b/pkg/domain/entities/engine.go
index 1f056bad7..6776d09e9 100644
--- a/pkg/domain/entities/engine.go
+++ b/pkg/domain/entities/engine.go
@@ -41,9 +41,8 @@ type PodmanConfig struct {
ConmonPath string // --conmon flag will set Engine.ConmonPath
CPUProfile string // Hidden: Should CPU profile be taken
EngineMode EngineMode // ABI or Tunneling mode
- Identities []string // ssh identities for connecting to server
+ Identity string // ssh identity for connecting to server
MaxWorks int // maximum number of parallel threads
- PassPhrase string // ssh passphrase for identity for connecting to server
RegistriesConf string // allows for specifying a custom registries.conf
Remote bool // Connection to Podman API Service will use RESTful API
RuntimePath string // --runtime flag will set Engine.RuntimePath
diff --git a/pkg/domain/infra/runtime_abi.go b/pkg/domain/infra/runtime_abi.go
index 60d0c6e86..3b344cb08 100644
--- a/pkg/domain/infra/runtime_abi.go
+++ b/pkg/domain/infra/runtime_abi.go
@@ -20,7 +20,7 @@ func NewContainerEngine(facts *entities.PodmanConfig) (entities.ContainerEngine,
r, err := NewLibpodRuntime(facts.FlagSet, facts)
return r, err
case entities.TunnelMode:
- ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.PassPhrase, facts.Identities...)
+ ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity)
return &tunnel.ContainerEngine{ClientCxt: ctx}, err
}
return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode)
@@ -33,7 +33,7 @@ func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error)
r, err := NewLibpodImageRuntime(facts.FlagSet, facts)
return r, err
case entities.TunnelMode:
- ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.PassPhrase, facts.Identities...)
+ ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity)
return &tunnel.ImageEngine{ClientCxt: ctx}, err
}
return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode)
diff --git a/pkg/domain/infra/runtime_tunnel.go b/pkg/domain/infra/runtime_tunnel.go
index 24a93b888..039a8339b 100644
--- a/pkg/domain/infra/runtime_tunnel.go
+++ b/pkg/domain/infra/runtime_tunnel.go
@@ -16,7 +16,7 @@ func NewContainerEngine(facts *entities.PodmanConfig) (entities.ContainerEngine,
case entities.ABIMode:
return nil, fmt.Errorf("direct runtime not supported")
case entities.TunnelMode:
- ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.PassPhrase, facts.Identities...)
+ ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity)
return &tunnel.ContainerEngine{ClientCxt: ctx}, err
}
return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode)
@@ -28,7 +28,7 @@ func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error)
case entities.ABIMode:
return nil, fmt.Errorf("direct image runtime not supported")
case entities.TunnelMode:
- ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.PassPhrase, facts.Identities...)
+ ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity)
return &tunnel.ImageEngine{ClientCxt: ctx}, err
}
return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode)
diff --git a/pkg/terminal/util.go b/pkg/terminal/util.go
new file mode 100644
index 000000000..ab3dc54e4
--- /dev/null
+++ b/pkg/terminal/util.go
@@ -0,0 +1,133 @@
+package terminal
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "sync"
+
+ "github.com/sirupsen/logrus"
+ "golang.org/x/crypto/ssh"
+ "golang.org/x/crypto/ssh/terminal"
+ "k8s.io/client-go/util/homedir"
+)
+
+var (
+ passPhrase []byte
+ phraseSync sync.Once
+ password []byte
+ passwordSync sync.Once
+)
+
+// ReadPassword prompts for a secret and returns value input by user from stdin
+// Unlike terminal.ReadPassword(), $(echo $SECRET | podman...) is supported.
+// Additionally, all input after `<secret>/n` is queued to podman command.
+func ReadPassword(prompt string) (pw []byte, err error) {
+ fd := int(os.Stdin.Fd())
+ if terminal.IsTerminal(fd) {
+ fmt.Fprint(os.Stderr, prompt)
+ pw, err = terminal.ReadPassword(fd)
+ fmt.Fprintln(os.Stderr)
+ return
+ }
+
+ var b [1]byte
+ for {
+ n, err := os.Stdin.Read(b[:])
+ // terminal.ReadPassword discards any '\r', so we do the same
+ if n > 0 && b[0] != '\r' {
+ if b[0] == '\n' {
+ return pw, nil
+ }
+ pw = append(pw, b[0])
+ // limit size, so that a wrong input won't fill up the memory
+ if len(pw) > 1024 {
+ err = errors.New("password too long, 1024 byte limit")
+ }
+ }
+ if err != nil {
+ // terminal.ReadPassword accepts EOF-terminated passwords
+ // if non-empty, so we do the same
+ if err == io.EOF && len(pw) > 0 {
+ err = nil
+ }
+ return pw, err
+ }
+ }
+}
+
+func PublicKey(path string, passphrase []byte) (ssh.AuthMethod, error) {
+ key, err := ioutil.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+
+ signer, err := ssh.ParsePrivateKey(key)
+ if err != nil {
+ if _, ok := err.(*ssh.PassphraseMissingError); !ok {
+ return nil, err
+ }
+ if len(passphrase) == 0 {
+ passphrase = ReadPassphrase()
+ }
+ signer, err = ssh.ParsePrivateKeyWithPassphrase(key, passphrase)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return ssh.PublicKeys(signer), nil
+}
+
+func ReadPassphrase() []byte {
+ phraseSync.Do(func() {
+ secret, err := ReadPassword("Key Passphrase: ")
+ if err != nil {
+ secret = []byte{}
+ }
+ passPhrase = secret
+ })
+ return passPhrase
+}
+
+func ReadLogin() []byte {
+ passwordSync.Do(func() {
+ secret, err := ReadPassword("Login password: ")
+ if err != nil {
+ secret = []byte{}
+ }
+ password = secret
+ })
+ return password
+}
+
+func HostKey(host string) ssh.PublicKey {
+ // parse OpenSSH known_hosts file
+ // ssh or use ssh-keyscan to get initial key
+ knownHosts := filepath.Join(homedir.HomeDir(), ".ssh", "known_hosts")
+ fd, err := os.Open(knownHosts)
+ if err != nil {
+ logrus.Error(err)
+ return nil
+ }
+
+ scanner := bufio.NewScanner(fd)
+ for scanner.Scan() {
+ _, hosts, key, _, _, err := ssh.ParseKnownHosts(scanner.Bytes())
+ if err != nil {
+ logrus.Errorf("Failed to parse known_hosts: %s", scanner.Text())
+ continue
+ }
+
+ for _, h := range hosts {
+ if h == host {
+ return key
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go
index 4fad85f00..afba12ccd 100644
--- a/test/e2e/run_networking_test.go
+++ b/test/e2e/run_networking_test.go
@@ -71,10 +71,9 @@ var _ = Describe("Podman run networking", func() {
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(80)))
- Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
- Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp"))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal(""))
+ Expect(len(inspectOut[0].NetworkSettings.Ports["80/tcp"])).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostPort).To(Equal("80"))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostIP).To(Equal(""))
})
It("podman run -p 8080:80", func() {
@@ -84,10 +83,9 @@ var _ = Describe("Podman run networking", func() {
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(8080)))
- Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
- Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp"))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal(""))
+ Expect(len(inspectOut[0].NetworkSettings.Ports["80/tcp"])).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostPort).To(Equal("8080"))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostIP).To(Equal(""))
})
It("podman run -p 80/udp", func() {
@@ -97,10 +95,9 @@ var _ = Describe("Podman run networking", func() {
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(80)))
- Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
- Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("udp"))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal(""))
+ Expect(len(inspectOut[0].NetworkSettings.Ports["80/udp"])).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/udp"][0].HostPort).To(Equal("80"))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/udp"][0].HostIP).To(Equal(""))
})
It("podman run -p 127.0.0.1:8080:80", func() {
@@ -110,10 +107,9 @@ var _ = Describe("Podman run networking", func() {
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(8080)))
- Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
- Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp"))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal("127.0.0.1"))
+ Expect(len(inspectOut[0].NetworkSettings.Ports["80/tcp"])).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostPort).To(Equal("8080"))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostIP).To(Equal("127.0.0.1"))
})
It("podman run -p 127.0.0.1:8080:80/udp", func() {
@@ -123,10 +119,9 @@ var _ = Describe("Podman run networking", func() {
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(8080)))
- Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
- Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("udp"))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal("127.0.0.1"))
+ Expect(len(inspectOut[0].NetworkSettings.Ports["80/udp"])).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/udp"][0].HostPort).To(Equal("8080"))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/udp"][0].HostIP).To(Equal("127.0.0.1"))
})
It("podman run -p [::1]:8080:80/udp", func() {
@@ -136,10 +131,9 @@ var _ = Describe("Podman run networking", func() {
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(8080)))
- Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
- Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("udp"))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal("::1"))
+ Expect(len(inspectOut[0].NetworkSettings.Ports["80/udp"])).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/udp"][0].HostPort).To(Equal("8080"))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/udp"][0].HostIP).To(Equal("::1"))
})
It("podman run -p [::1]:8080:80/tcp", func() {
@@ -149,10 +143,9 @@ var _ = Describe("Podman run networking", func() {
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(8080)))
- Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
- Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp"))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal("::1"))
+ Expect(len(inspectOut[0].NetworkSettings.Ports["80/tcp"])).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostPort).To(Equal("8080"))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostIP).To(Equal("::1"))
})
It("podman run --expose 80 -P", func() {
@@ -162,10 +155,9 @@ var _ = Describe("Podman run networking", func() {
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Not(Equal(int32(0))))
- Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
- Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp"))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal(""))
+ Expect(len(inspectOut[0].NetworkSettings.Ports["80/tcp"])).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostPort).To(Not(Equal("0")))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostIP).To(Equal(""))
})
It("podman run --expose 80/udp -P", func() {
@@ -175,10 +167,9 @@ var _ = Describe("Podman run networking", func() {
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Not(Equal(int32(0))))
- Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
- Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("udp"))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal(""))
+ Expect(len(inspectOut[0].NetworkSettings.Ports["80/udp"])).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/udp"][0].HostPort).To(Not(Equal("0")))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/udp"][0].HostIP).To(Equal(""))
})
It("podman run --expose 80 -p 80", func() {
@@ -188,10 +179,9 @@ var _ = Describe("Podman run networking", func() {
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(80)))
- Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
- Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp"))
- Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal(""))
+ Expect(len(inspectOut[0].NetworkSettings.Ports["80/tcp"])).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostPort).To(Equal("80"))
+ Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostIP).To(Equal(""))
})
It("podman run network expose host port 80 to container port 8000", func() {
@@ -214,7 +204,7 @@ var _ = Describe("Podman run networking", func() {
results := podmanTest.Podman([]string{"inspect", "-l"})
results.Wait(30)
Expect(results.ExitCode()).To(Equal(0))
- Expect(results.OutputToString()).To(ContainSubstring(": 80,"))
+ Expect(results.OutputToString()).To(ContainSubstring(`"80/tcp":`))
})
It("podman run network expose duplicate host port results in error", func() {
@@ -229,7 +219,9 @@ var _ = Describe("Podman run networking", func() {
Expect(inspect.ExitCode()).To(Equal(0))
containerConfig := inspect.InspectContainerToJSON()
- Expect(containerConfig[0].NetworkSettings.Ports[0].HostPort).ToNot(Equal(80))
+ Expect(containerConfig[0].NetworkSettings.Ports).To(Not(BeNil()))
+ Expect(containerConfig[0].NetworkSettings.Ports["80/tcp"]).To(Not(BeNil()))
+ Expect(containerConfig[0].NetworkSettings.Ports["80/tcp"][0].HostPort).ToNot(Equal(80))
})
It("podman run hostname test", func() {
diff --git a/test/e2e/run_selinux_test.go b/test/e2e/run_selinux_test.go
index 358137aa9..8b33a05b2 100644
--- a/test/e2e/run_selinux_test.go
+++ b/test/e2e/run_selinux_test.go
@@ -177,4 +177,13 @@ var _ = Describe("Podman run", func() {
Expect(session.OutputToString()).To(Equal(session1.OutputToString()))
})
+ It("podman run --privileged and --security-opt SELinux options", func() {
+ session := podmanTest.Podman([]string{"run", "-it", "--privileged", "--security-opt", "label=type:spc_t", "--security-opt", "label=level:s0:c1,c2", ALPINE, "cat", "/proc/self/attr/current"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ match, _ := session.GrepString("spc_t")
+ Expect(match).To(BeTrue())
+ match2, _ := session.GrepString("s0:c1,c2")
+ Expect(match2).To(BeTrue())
+ })
})
diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go
index 90179964d..42754bab4 100644
--- a/test/e2e/run_test.go
+++ b/test/e2e/run_test.go
@@ -1039,4 +1039,12 @@ USER mail`
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
})
+
+ It("podman run --privileged and --group-add", func() {
+ groupName := "kvm"
+ session := podmanTest.Podman([]string{"run", "-t", "-i", "--group-add", groupName, "--privileged", fedoraMinimal, "groups"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(strings.Contains(session.OutputToString(), groupName)).To(BeTrue())
+ })
})
diff --git a/test/e2e/run_userns_test.go b/test/e2e/run_userns_test.go
index 5b9a99daa..be0981408 100644
--- a/test/e2e/run_userns_test.go
+++ b/test/e2e/run_userns_test.go
@@ -89,6 +89,13 @@ var _ = Describe("Podman UserNS support", func() {
Expect(ok).To(BeTrue())
})
+ It("podman --userns=keep-id root owns /usr", func() {
+ session := podmanTest.Podman([]string{"run", "--userns=keep-id", "alpine", "stat", "-c%u", "/usr"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(Equal("0"))
+ })
+
It("podman --userns=keep-id --user root:root", func() {
session := podmanTest.Podman([]string{"run", "--userns=keep-id", "--user", "root:root", "alpine", "id", "-u"})
session.WaitWithDefaultTimeout()
diff --git a/test/e2e/untag_test.go b/test/e2e/untag_test.go
index dc1a6208e..8a1c8091d 100644
--- a/test/e2e/untag_test.go
+++ b/test/e2e/untag_test.go
@@ -23,13 +23,6 @@ var _ = Describe("Podman untag", func() {
podmanTest = PodmanTestCreate(tempdir)
podmanTest.Setup()
podmanTest.RestoreAllArtifacts()
-
- for _, tag := range []string{"test", "foo", "bar"} {
- session := podmanTest.PodmanNoCache([]string{"tag", ALPINE, tag})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- }
-
})
AfterEach(func() {
@@ -40,34 +33,63 @@ var _ = Describe("Podman untag", func() {
})
It("podman untag all", func() {
- session := podmanTest.PodmanNoCache([]string{"untag", ALPINE})
+ tags := []string{ALPINE, "registry.com/foo:bar", "localhost/foo:bar"}
+
+ cmd := []string{"tag"}
+ cmd = append(cmd, tags...)
+ session := podmanTest.PodmanNoCache(cmd)
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- results := podmanTest.PodmanNoCache([]string{"images", ALPINE})
- results.WaitWithDefaultTimeout()
- Expect(results.ExitCode()).To(Equal(0))
- Expect(results.OutputToStringArray()).To(HaveLen(1))
- })
+ // Make sure that all tags exists.
+ for _, t := range tags {
+ session = podmanTest.PodmanNoCache([]string{"image", "exists", t})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ }
- It("podman untag single", func() {
- session := podmanTest.PodmanNoCache([]string{"untag", ALPINE, "localhost/test:latest"})
+ // No arguments -> remove all tags.
+ session = podmanTest.PodmanNoCache([]string{"untag", ALPINE})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- results := podmanTest.PodmanNoCache([]string{"images"})
- results.WaitWithDefaultTimeout()
- Expect(results.ExitCode()).To(Equal(0))
- Expect(results.OutputToStringArray()).To(HaveLen(6))
- Expect(results.LineInOuputStartsWith("docker.io/library/alpine")).To(BeTrue())
- Expect(results.LineInOuputStartsWith("localhost/foo")).To(BeTrue())
- Expect(results.LineInOuputStartsWith("localhost/bar")).To(BeTrue())
- Expect(results.LineInOuputStartsWith("localhost/test")).To(BeFalse())
+ // Make sure that none of tags exists anymore.
+ for _, t := range tags {
+ session = podmanTest.PodmanNoCache([]string{"image", "exists", t})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(1))
+ }
})
- It("podman untag not enough arguments", func() {
- session := podmanTest.PodmanNoCache([]string{"untag"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).NotTo(Equal(0))
+ It("podman tag/untag - tag normalization", func() {
+ tests := []struct {
+ tag, normalized string
+ }{
+ {"registry.com/image:latest", "registry.com/image:latest"},
+ {"registry.com/image", "registry.com/image:latest"},
+ {"image:latest", "localhost/image:latest"},
+ {"image", "localhost/image:latest"},
+ }
+
+ // Make sure that the user input is normalized correctly for
+ // `podman tag` and `podman untag`.
+ for _, tt := range tests {
+ session := podmanTest.PodmanNoCache([]string{"tag", ALPINE, tt.tag})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.PodmanNoCache([]string{"image", "exists", tt.normalized})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.PodmanNoCache([]string{"untag", ALPINE, tt.tag})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.PodmanNoCache([]string{"image", "exists", tt.normalized})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(1))
+ }
})
+
})
diff --git a/test/system/015-help.bats b/test/system/015-help.bats
index 42d3bd3ec..14af8e1a4 100644
--- a/test/system/015-help.bats
+++ b/test/system/015-help.bats
@@ -25,7 +25,7 @@ function podman_commands() {
function check_help() {
local count=0
- local subcommands_found=0
+ local -A found
for cmd in $(podman_commands "$@"); do
# Human-readable podman command string, with multiple spaces collapsed
@@ -44,24 +44,37 @@ function check_help() {
# If usage ends in '[command]', recurse into subcommands
if expr "$usage" : '.*\[command\]$' >/dev/null; then
- subcommands_found=$(expr $subcommands_found + 1)
+ found[subcommands]=1
check_help "$@" $cmd
continue
fi
- # If usage ends in '[flag]', command takes no more arguments.
- # Confirm that by running with 'invalid-arg' and expecting failure.
- if expr "$usage" : '.*\[flags\]$' >/dev/null; then
+ # We had someone write upper-case '[FLAGS]' once. Prevent it.
+ if expr "$usage" : '.*\[FLAG' >/dev/null; then
+ die "'flags' string must be lower-case in usage: $usage"
+ fi
+
+ # We had someone do 'podman foo ARG [flags]' one time. Yeah, no.
+ if expr "$usage" : '.*[A-Z].*\[flag' >/dev/null; then
+ die "'flags' must precede arguments in usage: $usage"
+ fi
+
+ # If usage lists no arguments (strings in ALL CAPS), confirm
+ # by running with 'invalid-arg' and expecting failure.
+ if ! expr "$usage" : '.*[A-Z]' >/dev/null; then
if [ "$cmd" != "help" ]; then
dprint "$command_string invalid-arg"
run_podman 125 "$@" $cmd invalid-arg
is "$output" "Error: .* takes no arguments" \
"'$command_string' with extra (invalid) arguments"
fi
+ found[takes_no_args]=1
fi
- # If usage has required arguments, try running without them
- if expr "$usage" : '.*\[flags\] [A-Z]' >/dev/null; then
+ # If usage has required arguments, try running without them.
+ # The expression here is 'first capital letter is not in [BRACKETS]'.
+ # It is intended to handle 'podman foo [flags] ARG' but not ' [ARG]'.
+ if expr "$usage" : '[^A-Z]\+ [A-Z]' >/dev/null; then
# Exceptions: these commands don't work rootless
if is_rootless; then
# "pause is not supported for rootless containers"
@@ -80,6 +93,31 @@ function check_help() {
run_podman 125 "$@" $cmd </dev/null
is "$output" "Error:.* \(require\|specif\|must\|provide\|need\|choose\|accepts\)" \
"'$command_string' without required arg"
+
+ found[required_args]=1
+ fi
+
+ # Commands with fixed number of arguments (i.e. no ellipsis): count
+ # the required args, then invoke with one extra. We should get a
+ # usage error.
+ if ! expr "$usage" : ".*\.\.\."; then
+ # "podman help" can take infinite args, so skip that one
+ if [ "$cmd" != "help" ]; then
+ # Get the args part of the command line; this should be
+ # everything from the first CAPITAL LETTER onward. We
+ # don't actually care about the letter itself, so just
+ # make it 'X'. And we don't care about [OPTIONAL] brackets
+ # either. What we do care about is stuff like 'IMAGE | CTR'
+ # which is actually one argument; convert to 'IMAGE-or-CTR'
+ local rhs=$(sed -e 's/^[^A-Z]\+[A-Z]/X/' -e 's/ | /-or-/g' <<<"$usage")
+ local n_args=$(wc -w <<<"$rhs")
+
+ run_podman 125 "$@" $cmd $(seq --format='x%g' 0 $n_args)
+ is "$output" "Error:.* \(takes no arguments\|requires exactly $n_args arg\|accepts at most\|too many arguments\|accepts $n_args arg(s), received\|accepts between .* and .* arg(s), received \)" \
+ "'$command_string' with >$n_args arguments"
+
+ found[fixed_args]=1
+ fi
fi
count=$(expr $count + 1)
@@ -101,9 +139,14 @@ function check_help() {
[ $count -gt 0 ] || \
die "Internal error: no commands found in 'podman help $@' list"
- # At least the top level must have some subcommands
- if [ -z "$*" -a $subcommands_found -eq 0 ]; then
- die "Internal error: did not find any podman subcommands"
+ # Sanity check: make sure the special loops above triggered at least once.
+ # (We've had situations where a typo makes the conditional never run)
+ if [ -z "$*" ]; then
+ for i in subcommands required_args takes_no_args fixed_args; do
+ if [[ -z ${found[$i]} ]]; then
+ die "Internal error: '$i' subtest did not trigger"
+ fi
+ done
fi
}
diff --git a/test/system/020-tag.bats b/test/system/020-tag.bats
new file mode 100644
index 000000000..7593ad68f
--- /dev/null
+++ b/test/system/020-tag.bats
@@ -0,0 +1,35 @@
+#!/usr/bin/env bats
+
+load helpers
+
+# helper function for "podman tag/untag" test
+function _tag_and_check() {
+ local tag_as="$1"
+ local check_as="$2"
+
+ run_podman tag $IMAGE $tag_as
+ run_podman image exists $check_as
+ run_podman untag $IMAGE $check_as
+ run_podman 1 image exists $check_as
+}
+
+@test "podman tag/untag" {
+ # Test a fully-qualified image reference.
+ _tag_and_check registry.com/image:latest registry.com/image:latest
+
+ # Test a reference without tag and make sure ":latest" is appended.
+ _tag_and_check registry.com/image registry.com/image:latest
+
+ # Test a tagged short image and make sure "localhost/" is prepended.
+ _tag_and_check image:latest localhost/image:latest
+
+ # Test a short image without tag and make sure "localhost/" is
+ # prepended and ":latest" is appended.
+ _tag_and_check image localhost/image:latest
+
+ # Test error case.
+ run_podman 125 untag $IMAGE registry.com/foo:bar
+ is "$output" "Error: \"registry.com/foo:bar\": no such tag"
+}
+
+# vim: filetype=sh
diff --git a/test/system/120-load.bats b/test/system/120-load.bats
index f290c1888..afa5ab473 100644
--- a/test/system/120-load.bats
+++ b/test/system/120-load.bats
@@ -107,10 +107,4 @@ verify_iid_and_name() {
"Diagnostic from 'podman load' without redirection or -i"
}
-@test "podman load - at most 1 arg(s)" {
- run_podman 125 load 1 2 3
- is "$output" \
- "Error: accepts at most 1 arg(s), received 3"
-}
-
# vim: filetype=sh
diff --git a/test/system/helpers.bash b/test/system/helpers.bash
index 7e6f1c1ca..5301644d6 100644
--- a/test/system/helpers.bash
+++ b/test/system/helpers.bash
@@ -2,12 +2,6 @@
# Podman command to run; may be podman-remote
PODMAN=${PODMAN:-podman}
-# If it's a relative path, convert to absolute; otherwise tests can't cd out
-if [[ "$PODMAN" =~ / ]]; then
- if [[ ! "$PODMAN" =~ ^/ ]]; then
- PODMAN=$(realpath $PODMAN)
- fi
-fi
# Standard image to use for most tests
PODMAN_TEST_IMAGE_REGISTRY=${PODMAN_TEST_IMAGE_REGISTRY:-"quay.io"}
@@ -22,6 +16,12 @@ IMAGE=$PODMAN_TEST_IMAGE_FQN
# Default timeout for a podman command.
PODMAN_TIMEOUT=${PODMAN_TIMEOUT:-60}
+# Prompt to display when logging podman commands; distinguish root/rootless
+_LOG_PROMPT='$'
+if [ $(id -u) -eq 0 ]; then
+ _LOG_PROMPT='#'
+fi
+
###############################################################################
# BEGIN setup/teardown tools
@@ -138,7 +138,7 @@ function run_podman() {
esac
# stdout is only emitted upon error; this echo is to help a debugger
- echo "\$ $PODMAN $*"
+ echo "$_LOG_PROMPT $PODMAN $*"
# BATS hangs if a subprocess remains and keeps FD 3 open; this happens
# if podman crashes unexpectedly without cleaning up subprocesses.
run timeout --foreground -v --kill=10 $PODMAN_TIMEOUT $PODMAN "$@" 3>/dev/null