summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--RELEASE_NOTES.md23
-rw-r--r--cmd/podman/common/create.go3
-rw-r--r--cmd/podman/common/specgen.go18
-rw-r--r--cmd/podman/containers/create.go4
-rw-r--r--cmd/podman/containers/ps.go7
-rw-r--r--cmd/podman/images/build.go20
-rw-r--r--cmd/podman/images/load.go2
-rw-r--r--cmd/podman/main.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--completions/bash/podman36
-rw-r--r--docs/source/markdown/podman-auto-update.1.md2
-rw-r--r--docs/source/markdown/podman-generate-systemd.1.md10
-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--go.mod2
-rw-r--r--libpod/container_internal.go20
-rw-r--r--libpod/container_log.go29
-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--libpod/runtime_ctr.go4
-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/rootless/rootless_linux.go3
-rw-r--r--pkg/specgen/container_validate.go4
-rw-r--r--pkg/specgen/specgen.go1
-rw-r--r--pkg/systemd/generate/containers.go2
-rw-r--r--pkg/systemd/generate/containers_test.go10
-rw-r--r--pkg/systemd/generate/pods.go2
-rw-r--r--pkg/systemd/generate/pods_test.go2
-rw-r--r--pkg/terminal/util.go133
-rw-r--r--pkg/util/utils.go6
-rw-r--r--test/e2e/build_test.go17
-rw-r--r--test/e2e/create_test.go13
-rw-r--r--test/e2e/generate_systemd_test.go2
-rw-r--r--test/e2e/logs_test.go12
-rw-r--r--test/e2e/run_networking_test.go76
-rw-r--r--test/e2e/run_userns_test.go7
-rw-r--r--test/e2e/untag_test.go76
-rw-r--r--test/system/020-tag.bats35
-rw-r--r--test/system/120-load.bats18
55 files changed, 806 insertions, 317 deletions
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index b398d7d48..be9861518 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,5 +1,28 @@
# Release Notes
+## 2.0.1
+### Changes
+- The `podman system connection` command was mistakenly omitted from the 2.0 release, and has been included here.
+- The `podman ps --format=json` command once again includes container's creation time in a human-readable format in the `CreatedAt` key.
+- The `podman inspect` commands on containers now displays forwarded ports in a format compatible with `docker inspect`.
+
+### Bugfixes
+- Fixed a bug where `podman build` did not properly handle the `--http-proxy` and `--cgroup-manager` flags.
+- Fixed a bug where error messages related to a missing or inaccessible `/etc/subuid` or `/etc/subgid` file were very unclear ([#6572](https://github.com/containers/libpod/issues/6572)).
+- Fixed a bug where the `podman logs --follow` command would not stop when the container being followed exited.
+- Fixed a bug where the `--privileged` flag had mistakenly been marked as conflicting with `--group-add` and `--security-opt`.
+- Fixed a bug where the `PODMAN_USERNS` environment variable was not being honored ([#6705](https://github.com/containers/libpod/issues/6705)).
+- Fixed a bug where the `podman image load` command would require one argument be passed, when no arguments is also valid ([#6718](https://github.com/containers/libpod/issues/6718)).
+- Fixed a bug where the bash completions did not include the `podman network` command and its subcommands.
+- Fixed a bug where the mount command would not work inside of rootless containers ([#6735](https://github.com/containers/libpod/issues/6735)).
+- Fixed a bug where SSH agent authentication support was not properly working in the `podman-remote` and `podman --remote` commands.
+- Fixed a bug where the `podman untag` command was not erroring when no matching image was found.
+- Fixed a bug where stop signal for containers was not being set properly if not explicitly provided.
+
+### API
+- Fixed a bug where network endpoint URLs in the compatability API were mistakenly suffixed with `/json`.
+- Fixed a bug where the Libpod volume creation endpoint returned 200 instead of 201 on success.
+
## 2.0.0
### Features
- The REST API and `podman system service` are no longer experimental, and ready for use!
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go
index 921cd5a71..fbb7f449e 100644
--- a/cmd/podman/common/create.go
+++ b/cmd/podman/common/create.go
@@ -2,6 +2,7 @@ package common
import (
"fmt"
+ "os"
"github.com/containers/common/pkg/auth"
"github.com/containers/libpod/cmd/podman/registry"
@@ -464,7 +465,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
"Username or UID (format: <name|uid>[:<group|gid>])",
)
createFlags.String(
- "userns", "",
+ "userns", os.Getenv("PODMAN_USERNS"),
"User namespace to use",
)
createFlags.String(
diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go
index e6a524358..26d18faf0 100644
--- a/cmd/podman/common/specgen.go
+++ b/cmd/podman/common/specgen.go
@@ -535,7 +535,6 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
s.SeccompPolicy = c.SeccompPolicy
- // TODO: should parse out options
s.VolumesFrom = c.VolumesFrom
// Only add read-only tmpfs mounts in case that we are read-only and the
@@ -547,22 +546,10 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
s.Mounts = mounts
s.Volumes = volumes
- // TODO any idea why this was done
- // devices := rtc.Containers.Devices
- // TODO conflict on populate?
- //
- // if c.Changed("device") {
- // devices = append(devices, c.StringSlice("device")...)
- // }
-
for _, dev := range c.Devices {
s.Devices = append(s.Devices, specs.LinuxDevice{Path: dev})
}
- // TODO things i cannot find in spec
- // we dont think these are in the spec
- // init - initbinary
- // initpath
s.Init = c.Init
s.InitPath = c.InitPath
s.Stdin = c.Interactive
@@ -587,11 +574,6 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
s.Rlimits = append(s.Rlimits, rl)
}
- // Tmpfs: c.StringArray("tmpfs"),
-
- // TODO how to handle this?
- // Syslog: c.Bool("syslog"),
-
logOpts := make(map[string]string)
for _, o := range c.LogOptions {
split := strings.SplitN(o, "=", 2)
diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go
index 6269ec781..45ce00c86 100644
--- a/cmd/podman/containers/create.go
+++ b/cmd/podman/containers/create.go
@@ -156,10 +156,6 @@ func replaceContainer(name string) error {
}
func createInit(c *cobra.Command) error {
- if c.Flag("privileged").Changed && c.Flag("security-opt").Changed {
- logrus.Warn("setting security options with --privileged has no effect")
- }
-
if c.Flag("shm-size").Changed {
cliVals.ShmSize = c.Flag("shm-size").Value.String()
}
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/images/build.go b/cmd/podman/images/build.go
index 2efc795cd..23bfcab79 100644
--- a/cmd/podman/images/build.go
+++ b/cmd/podman/images/build.go
@@ -9,6 +9,7 @@ import (
"github.com/containers/buildah/imagebuildah"
buildahCLI "github.com/containers/buildah/pkg/cli"
"github.com/containers/buildah/pkg/parse"
+ "github.com/containers/common/pkg/config"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/cmd/podman/utils"
"github.com/containers/libpod/pkg/domain/entities"
@@ -396,16 +397,10 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil
runtimeFlags = append(runtimeFlags, "--"+arg)
}
- // FIXME: the code below needs to be enabled (and adjusted) once the
- // global/root flags are supported.
-
- // conf, err := runtime.GetConfig()
- // if err != nil {
- // return err
- // }
- // if conf != nil && conf.Engine.CgroupManager == config.SystemdCgroupsManager {
- // runtimeFlags = append(runtimeFlags, "--systemd-cgroup")
- // }
+ containerConfig := registry.PodmanConfig()
+ if containerConfig.Engine.CgroupManager == config.SystemdCgroupsManager {
+ runtimeFlags = append(runtimeFlags, "--systemd-cgroup")
+ }
opts := imagebuildah.BuildOptions{
AddCapabilities: flags.CapAdd,
@@ -418,12 +413,13 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil
CNIPluginPath: flags.CNIPlugInPath,
CommonBuildOpts: &buildah.CommonBuildOptions{
AddHost: flags.AddHost,
- CgroupParent: flags.CgroupParent,
CPUPeriod: flags.CPUPeriod,
CPUQuota: flags.CPUQuota,
- CPUShares: flags.CPUShares,
CPUSetCPUs: flags.CPUSetCPUs,
CPUSetMems: flags.CPUSetMems,
+ CPUShares: flags.CPUShares,
+ CgroupParent: flags.CgroupParent,
+ HTTPProxy: flags.HTTPProxy,
Memory: memoryLimit,
MemorySwap: memorySwap,
ShmSize: flags.ShmSize,
diff --git a/cmd/podman/images/load.go b/cmd/podman/images/load.go
index a984ad81f..115e9a070 100644
--- a/cmd/podman/images/load.go
+++ b/cmd/podman/images/load.go
@@ -30,7 +30,7 @@ var (
}
imageLoadCommand = &cobra.Command{
- Args: cobra.MinimumNArgs(1),
+ Args: loadCommand.Args,
Use: loadCommand.Use,
Short: loadCommand.Short,
Long: loadCommand.Long,
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/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..2fdfcf7c5
--- /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/completions/bash/podman b/completions/bash/podman
index 5e990ec41..abcf54416 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
@@ -2458,6 +2465,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
}
@@ -3564,6 +3588,7 @@ _podman_podman() {
logs
manifest
mount
+ network
pause
pod
port
@@ -3585,6 +3610,7 @@ _podman_podman() {
umount
unmount
unpause
+ untag
varlink
version
volume
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-generate-systemd.1.md b/docs/source/markdown/podman-generate-systemd.1.md
index 2facd754c..466c7e2bf 100644
--- a/docs/source/markdown/podman-generate-systemd.1.md
+++ b/docs/source/markdown/podman-generate-systemd.1.md
@@ -97,7 +97,7 @@ After=network-online.target
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=on-failure
-ExecStartPre=/usr/bin/rm -f %t/%n-pid %t/%n-cid
+ExecStartPre=/bin/rm -f %t/%n-pid %t/%n-cid
ExecStart=/usr/local/bin/podman run --conmon-pidfile %t/%n-pid --cidfile %t/%n-cid --cgroups=no-conmon -d -dit alpine
ExecStop=/usr/local/bin/podman stop --ignore --cidfile %t/%n-cid -t 10
ExecStopPost=/usr/local/bin/podman rm --ignore -f --cidfile %t/%n-cid
@@ -163,10 +163,10 @@ $ podman generate systemd --files --name systemd-pod
# Copy all the generated files.
$ sudo cp pod-systemd-pod.service container-great_payne.service /usr/lib/systemd/system
-$ systemctl enable pod-systemd-po.service
-Created symlink /etc/systemd/system/multi-user.target.wants/pod-systemd-po.service → /usr/lib/systemd/system/pod-systemd-po.service.
-Created symlink /etc/systemd/system/default.target.wants/pod-systemd-po.service → /usr/lib/systemd/system/pod-systemd-po.service.
-$ systemctl is-enabled pod-systemd-po.service
+$ systemctl enable pod-systemd-pod.service
+Created symlink /etc/systemd/system/multi-user.target.wants/pod-systemd-pod.service → /usr/lib/systemd/system/pod-systemd-pod.service.
+Created symlink /etc/systemd/system/default.target.wants/pod-systemd-pod.service → /usr/lib/systemd/system/pod-systemd-pod.service.
+$ systemctl is-enabled pod-systemd-pod.service
enabled
```
To run the user services placed in `$HOME/.config/systemd/user/` on first login of that user, enable the service with --user flag.
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/go.mod b/go.mod
index cfeffe8e5..d02dfa0b4 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/containers/libpod
-go 1.12
+go 1.13
require (
github.com/BurntSushi/toml v0.3.1
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/container_log.go b/libpod/container_log.go
index 071882bc2..67380397a 100644
--- a/libpod/container_log.go
+++ b/libpod/container_log.go
@@ -1,10 +1,13 @@
package libpod
import (
+ "fmt"
"os"
+ "time"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/logs"
+ "github.com/hpcloud/tail/watch"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -81,5 +84,31 @@ func (c *Container) readFromLogFile(options *logs.LogOptions, logChannel chan *l
}
options.WaitGroup.Done()
}()
+ // Check if container is still running or paused
+ if options.Follow {
+ go func() {
+ for {
+ state, err := c.State()
+ time.Sleep(watch.POLL_DURATION)
+ if err != nil {
+ tailError := t.StopAtEOF()
+ if tailError != nil && fmt.Sprintf("%v", tailError) != "tail: stop at eof" {
+ logrus.Error(tailError)
+ }
+ if errors.Cause(err) != define.ErrNoSuchCtr {
+ logrus.Error(err)
+ }
+ break
+ }
+ if state != define.ContainerStateRunning && state != define.ContainerStatePaused {
+ tailError := t.StopAtEOF()
+ if tailError != nil && fmt.Sprintf("%v", tailError) != "tail: stop at eof" {
+ logrus.Error(tailError)
+ }
+ break
+ }
+ }
+ }()
+ }
return nil
}
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/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index 0431861b5..f1752cbeb 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -83,6 +83,8 @@ func (r *Runtime) initContainerVariables(rSpec *spec.Spec, config *ContainerConf
return nil, errors.Wrapf(err, "converting containers.conf ShmSize %s to an int", r.config.Containers.ShmSize)
}
ctr.config.ShmSize = size
+ ctr.config.StopSignal = 15
+ ctr.config.StopTimeout = r.config.Engine.StopTimeout
} else {
// This is a restore from an imported checkpoint
ctr.restoreFromCheckpoint = true
@@ -107,8 +109,6 @@ func (r *Runtime) initContainerVariables(rSpec *spec.Spec, config *ContainerConf
ctr.state.BindMounts = make(map[string]string)
- ctr.config.StopTimeout = r.config.Engine.StopTimeout
-
ctr.config.OCIRuntime = r.defaultOCIRuntime.Name()
// Set namespace based on current runtime namespace
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/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go
index 3de136f12..01f5b1206 100644
--- a/pkg/rootless/rootless_linux.go
+++ b/pkg/rootless/rootless_linux.go
@@ -166,7 +166,8 @@ func GetConfiguredMappings() ([]idtools.IDMap, []idtools.IDMap, error) {
}
mappings, err := idtools.NewIDMappings(username, username)
if err != nil {
- logrus.Errorf("cannot find mappings for user %s: %v", username, err)
+ logrus.Errorf(
+ "cannot find UID/GID for user %s: %v - check rootless mode in man pages.", username, err)
} else {
uids = mappings.UIDs()
gids = mappings.GIDs()
diff --git a/pkg/specgen/container_validate.go b/pkg/specgen/container_validate.go
index 45179343b..33bacecaf 100644
--- a/pkg/specgen/container_validate.go
+++ b/pkg/specgen/container_validate.go
@@ -61,10 +61,6 @@ func (s *SpecGenerator) Validate() error {
//
// ContainerSecurityConfig
//
- // groups and privileged are exclusive
- if len(s.Groups) > 0 && s.Privileged {
- return exclusiveOptions("Groups", "privileged")
- }
// capadd and privileged are exclusive
if len(s.CapAdd) > 0 && s.Privileged {
return exclusiveOptions("CapAdd", "privileged")
diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go
index bb01a5d14..77b1353c4 100644
--- a/pkg/specgen/specgen.go
+++ b/pkg/specgen/specgen.go
@@ -207,6 +207,7 @@ type ContainerSecurityConfig struct {
// - Adds all devices on the system to the container.
// - Adds all capabilities to the container.
// - Disables Seccomp, SELinux, and Apparmor confinement.
+ // (Though SELinux can be manually re-enabled).
// TODO: this conflicts with things.
// TODO: this does more.
Privileged bool `json:"privileged,omitempty"`
diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go
index 16ff0b821..bf6cb81b8 100644
--- a/pkg/systemd/generate/containers.go
+++ b/pkg/systemd/generate/containers.go
@@ -244,7 +244,7 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst
}
startCommand = append(startCommand, info.CreateCommand[index:]...)
- info.ExecStartPre = "/usr/bin/rm -f {{.PIDFile}} {{.ContainerIDFile}}"
+ info.ExecStartPre = "/bin/rm -f {{.PIDFile}} {{.ContainerIDFile}}"
info.ExecStart = strings.Join(startCommand, " ")
info.ExecStop = "{{.Executable}} stop --ignore --cidfile {{.ContainerIDFile}} {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}}"
info.ExecStopPost = "{{.Executable}} rm --ignore -f --cidfile {{.ContainerIDFile}}"
diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/containers_test.go
index 5f35c31f5..80f0996a1 100644
--- a/pkg/systemd/generate/containers_test.go
+++ b/pkg/systemd/generate/containers_test.go
@@ -118,7 +118,7 @@ After=network-online.target
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
-ExecStartPre=/usr/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id
+ExecStartPre=/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id
ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon -d --replace --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN
ExecStop=/usr/bin/podman stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 42
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/jadda-jadda.ctr-id
@@ -141,7 +141,7 @@ After=network-online.target
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
-ExecStartPre=/usr/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id
+ExecStartPre=/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id
ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon --replace -d --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN
ExecStop=/usr/bin/podman stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 42
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/jadda-jadda.ctr-id
@@ -164,7 +164,7 @@ After=network-online.target
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
-ExecStartPre=/usr/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id
+ExecStartPre=/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id
ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon --pod-id-file /tmp/pod-foobar.pod-id-file --replace -d --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN
ExecStop=/usr/bin/podman stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 42
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/jadda-jadda.ctr-id
@@ -187,7 +187,7 @@ After=network-online.target
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
-ExecStartPre=/usr/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id
+ExecStartPre=/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id
ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon --replace --detach --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN
ExecStop=/usr/bin/podman stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 42
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/jadda-jadda.ctr-id
@@ -210,7 +210,7 @@ After=network-online.target
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=always
-ExecStartPre=/usr/bin/rm -f %t/container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.pid %t/container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.ctr-id
+ExecStartPre=/bin/rm -f %t/container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.pid %t/container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.ctr-id
ExecStart=/usr/bin/podman run --conmon-pidfile %t/container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.pid --cidfile %t/container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.ctr-id --cgroups=no-conmon -d awesome-image:latest
ExecStop=/usr/bin/podman stop --ignore --cidfile %t/container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.ctr-id -t 10
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.ctr-id
diff --git a/pkg/systemd/generate/pods.go b/pkg/systemd/generate/pods.go
index 1bd0c7bce..cb4078fac 100644
--- a/pkg/systemd/generate/pods.go
+++ b/pkg/systemd/generate/pods.go
@@ -293,7 +293,7 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions)
startCommand = append(startCommand, podCreateArgs...)
- info.ExecStartPre1 = "/usr/bin/rm -f {{.PIDFile}} {{.PodIDFile}}"
+ info.ExecStartPre1 = "/bin/rm -f {{.PIDFile}} {{.PodIDFile}}"
info.ExecStartPre2 = strings.Join(startCommand, " ")
info.ExecStart = "{{.Executable}} pod start --pod-id-file {{.PodIDFile}}"
info.ExecStop = "{{.Executable}} pod stop --ignore --pod-id-file {{.PodIDFile}} {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}}"
diff --git a/pkg/systemd/generate/pods_test.go b/pkg/systemd/generate/pods_test.go
index e12222317..874d7204e 100644
--- a/pkg/systemd/generate/pods_test.go
+++ b/pkg/systemd/generate/pods_test.go
@@ -74,7 +74,7 @@ Before=container-1.service container-2.service
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=on-failure
-ExecStartPre=/usr/bin/rm -f %t/pod-123abc.pid %t/pod-123abc.pod-id
+ExecStartPre=/bin/rm -f %t/pod-123abc.pid %t/pod-123abc.pod-id
ExecStartPre=/usr/bin/podman pod create --infra-conmon-pidfile %t/pod-123abc.pid --pod-id-file %t/pod-123abc.pod-id --name foo --replace
ExecStart=/usr/bin/podman pod start --pod-id-file %t/pod-123abc.pod-id
ExecStop=/usr/bin/podman pod stop --ignore --pod-id-file %t/pod-123abc.pod-id -t 10
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/pkg/util/utils.go b/pkg/util/utils.go
index 917f57742..1d8941b4d 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -419,12 +419,6 @@ func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []strin
if len(uidMapSlice) == 0 && len(gidMapSlice) != 0 {
uidMapSlice = gidMapSlice
}
- if len(uidMapSlice) == 0 && subUIDMap == "" && os.Getuid() != 0 {
- uidMapSlice = []string{fmt.Sprintf("0:%d:1", os.Getuid())}
- }
- if len(gidMapSlice) == 0 && subGIDMap == "" && os.Getuid() != 0 {
- gidMapSlice = []string{fmt.Sprintf("0:%d:1", os.Getgid())}
- }
if subUIDMap != "" && subGIDMap != "" {
mappings, err := idtools.NewIDMappings(subUIDMap, subGIDMap)
diff --git a/test/e2e/build_test.go b/test/e2e/build_test.go
index 9e41fd231..0cf5283ad 100644
--- a/test/e2e/build_test.go
+++ b/test/e2e/build_test.go
@@ -195,4 +195,21 @@ var _ = Describe("Podman build", func() {
Expect(session.ExitCode()).To(Equal(0))
})
+ It("podman build --http_proxy flag", func() {
+ SkipIfRemote()
+ os.Setenv("http_proxy", "1.2.3.4")
+ podmanTest.RestoreAllArtifacts()
+ dockerfile := `FROM docker.io/library/alpine:latest
+RUN printenv http_proxy`
+
+ dockerfilePath := filepath.Join(podmanTest.TempDir, "Dockerfile")
+ err := ioutil.WriteFile(dockerfilePath, []byte(dockerfile), 0755)
+ Expect(err).To(BeNil())
+ session := podmanTest.PodmanNoCache([]string{"build", "--file", dockerfilePath, podmanTest.TempDir})
+ session.Wait(120)
+ Expect(session.ExitCode()).To(Equal(0))
+ ok, _ := session.GrepString("1.2.3.4")
+ Expect(ok).To(BeTrue())
+ os.Unsetenv("http_proxy")
+ })
})
diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go
index 52ce0b46a..44bb5c45f 100644
--- a/test/e2e/create_test.go
+++ b/test/e2e/create_test.go
@@ -458,4 +458,17 @@ var _ = Describe("Podman create", func() {
Expect(session.ExitCode()).To(Equal(0))
}
})
+
+ It("podman create sets default stop signal 15", func() {
+ ctrName := "testCtr"
+ session := podmanTest.Podman([]string{"create", "--name", ctrName, ALPINE, "/bin/sh"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ inspect := podmanTest.Podman([]string{"inspect", ctrName})
+ inspect.WaitWithDefaultTimeout()
+ data := inspect.InspectContainerToJSON()
+ Expect(len(data)).To(Equal(1))
+ Expect(data[0].Config.StopSignal).To(Equal(uint(15)))
+ })
})
diff --git a/test/e2e/generate_systemd_test.go b/test/e2e/generate_systemd_test.go
index 497e8f71e..f43a4f865 100644
--- a/test/e2e/generate_systemd_test.go
+++ b/test/e2e/generate_systemd_test.go
@@ -362,7 +362,7 @@ var _ = Describe("Podman generate systemd", func() {
found, _ = session.GrepString("pod create --infra-conmon-pidfile %t/pod-foo.pid --pod-id-file %t/pod-foo.pod-id --name foo")
Expect(found).To(BeTrue())
- found, _ = session.GrepString("ExecStartPre=/usr/bin/rm -f %t/pod-foo.pid %t/pod-foo.pod-id")
+ found, _ = session.GrepString("ExecStartPre=/bin/rm -f %t/pod-foo.pid %t/pod-foo.pod-id")
Expect(found).To(BeTrue())
found, _ = session.GrepString("pod stop --ignore --pod-id-file %t/pod-foo.pod-id -t 10")
diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go
index a4a59acb2..cf69cbd3e 100644
--- a/test/e2e/logs_test.go
+++ b/test/e2e/logs_test.go
@@ -311,4 +311,16 @@ var _ = Describe("Podman logs", func() {
logs.WaitWithDefaultTimeout()
Expect(logs).To(Not(Exit(0)))
})
+
+ It("follow output stopped container", func() {
+ containerName := "logs-f"
+
+ logc := podmanTest.Podman([]string{"run", "--name", containerName, "-d", ALPINE, "true"})
+ logc.WaitWithDefaultTimeout()
+ Expect(logc).To(Exit(0))
+
+ results := podmanTest.Podman([]string{"logs", "-f", containerName})
+ results.WaitWithDefaultTimeout()
+ Expect(results).To(Exit(0))
+ })
})
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_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/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 15df6adec..f290c1888 100644
--- a/test/system/120-load.bats
+++ b/test/system/120-load.bats
@@ -44,6 +44,11 @@ verify_iid_and_name() {
run_podman load < $archive
verify_iid_and_name "<none>:<none>"
+ # Same as above, using stdin but with `podman image load`
+ run_podman rmi $iid
+ run_podman image load < $archive
+ verify_iid_and_name "<none>:<none>"
+
# Cleanup: since load-by-iid doesn't preserve name, re-tag it;
# otherwise our global teardown will rmi and re-pull our standard image.
run_podman tag $iid $img_name
@@ -57,9 +62,14 @@ verify_iid_and_name() {
# Load using -i; this time the image should be tagged.
run_podman load -i $archive
verify_iid_and_name $img_name
+ run_podman rmi $iid
- # Same as above, using stdin
+ # Also make sure that `image load` behaves the same.
+ run_podman image load -i $archive
+ verify_iid_and_name $img_name
run_podman rmi $iid
+
+ # Same as above, using stdin
run_podman load < $archive
verify_iid_and_name $img_name
}
@@ -97,4 +107,10 @@ 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