diff options
36 files changed, 1171 insertions, 511 deletions
diff --git a/cmd/podman/containers/start.go b/cmd/podman/containers/start.go index 21f22b986..8f9984421 100644 --- a/cmd/podman/containers/start.go +++ b/cmd/podman/containers/start.go @@ -99,7 +99,7 @@ func start(cmd *cobra.Command, args []string) error { } for _, r := range responses { - if r.Err == nil { + if r.Err == nil && !startOptions.Attach { fmt.Println(r.RawInput) } else { errs = append(errs, r.Err) diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 5f740a006..f46f74547 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -14,6 +14,7 @@ import ( _ "github.com/containers/libpod/v2/cmd/podman/pods" "github.com/containers/libpod/v2/cmd/podman/registry" _ "github.com/containers/libpod/v2/cmd/podman/system" + _ "github.com/containers/libpod/v2/cmd/podman/system/connection" _ "github.com/containers/libpod/v2/cmd/podman/volumes" "github.com/containers/libpod/v2/pkg/rootless" "github.com/containers/libpod/v2/pkg/terminal" diff --git a/cmd/podman/networks/inspect.go b/cmd/podman/networks/inspect.go index bf2c7a5e0..bfbb09cb8 100644 --- a/cmd/podman/networks/inspect.go +++ b/cmd/podman/networks/inspect.go @@ -3,10 +3,10 @@ package network import ( "encoding/json" "fmt" - "html/template" "io" "os" "strings" + "text/template" "github.com/containers/libpod/v2/cmd/podman/registry" "github.com/containers/libpod/v2/pkg/domain/entities" diff --git a/cmd/podman/networks/list.go b/cmd/podman/networks/list.go index ad2ee98b1..105bd25c6 100644 --- a/cmd/podman/networks/list.go +++ b/cmd/podman/networks/list.go @@ -3,10 +3,10 @@ package network import ( "encoding/json" "fmt" - "html/template" "os" "strings" "text/tabwriter" + "text/template" "github.com/containers/libpod/v2/cmd/podman/registry" "github.com/containers/libpod/v2/cmd/podman/validate" diff --git a/cmd/podman/root.go b/cmd/podman/root.go index c6ced21c0..e9f1ff710 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -236,16 +236,12 @@ func loggingHook() { func rootFlags(cmd *cobra.Command, opts *entities.PodmanConfig) { cfg := opts.Config + uri, ident := resolveDestination() lFlags := cmd.Flags() - custom, _ := config.ReadCustomConfig() - defaultURI := custom.Engine.RemoteURI - if defaultURI == "" { - defaultURI = registry.DefaultAPIAddress() - } lFlags.BoolVarP(&opts.Remote, "remote", "r", false, "Access remote Podman service (default false)") - lFlags.StringVar(&opts.URI, "url", defaultURI, "URL to access Podman service (CONTAINER_HOST)") - lFlags.StringVar(&opts.Identity, "identity", custom.Engine.RemoteIdentity, "path to SSH identity file, (CONTAINER_SSHKEY)") + lFlags.StringVar(&opts.URI, "url", uri, "URL to access Podman service (CONTAINER_HOST)") + lFlags.StringVar(&opts.Identity, "identity", ident, "path to SSH identity file, (CONTAINER_SSHKEY)") pFlags := cmd.PersistentFlags() pFlags.StringVar(&cfg.Engine.CgroupManager, "cgroup-manager", cfg.Engine.CgroupManager, "Cgroup manager to use (\"cgroupfs\"|\"systemd\")") @@ -292,3 +288,24 @@ func rootFlags(cmd *cobra.Command, opts *entities.PodmanConfig) { pFlags.BoolVar(&useSyslog, "syslog", false, "Output logging information to syslog as well as the console (default false)") } } + +func resolveDestination() (string, string) { + if uri, found := os.LookupEnv("CONTAINER_HOST"); found { + var ident string + if v, found := os.LookupEnv("CONTAINER_SSHKEY"); found { + ident = v + } + return uri, ident + } + + cfg, err := config.ReadCustomConfig() + if err != nil { + return registry.DefaultAPIAddress(), "" + } + + uri, ident, err := cfg.ActiveDestination() + if err != nil { + return registry.DefaultAPIAddress(), "" + } + return uri, ident +} diff --git a/cmd/podman/system/connection.go b/cmd/podman/system/connection.go index 9f26a0df6..b1c538803 100644 --- a/cmd/podman/system/connection.go +++ b/cmd/podman/system/connection.go @@ -1,209 +1,34 @@ package system import ( - "bytes" - "fmt" - "net" - "net/url" - "os" - "os/user" - "regexp" - - "github.com/containers/common/pkg/config" "github.com/containers/libpod/v2/cmd/podman/registry" - "github.com/containers/libpod/v2/libpod/define" + "github.com/containers/libpod/v2/cmd/podman/validate" "github.com/containers/libpod/v2/pkg/domain/entities" - "github.com/containers/libpod/v2/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 + // Skip creating engines since this command will obtain connection information to said engines 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 - }{} + ConnectionCmd = &cobra.Command{ + Use: "connection", + Short: "Manage remote ssh destinations", + Long: `Manage ssh destination information in podman configuration`, + DisableFlagsInUseLine: true, + PersistentPreRunE: noOp, + RunE: validate.SubCommandExists, + PersistentPostRunE: noOp, + TraverseChildren: false, + } ) func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, - Command: connectionCmd, + Command: ConnectionCmd, Parent: systemCmd, }) - - flags := connectionCmd.Flags() - flags.IntVarP(&cOpts.Port, "port", "p", 22, "SSH port number for destination") - flags.StringVar(&cOpts.Identity, "identity", "", "path to SSH identity file") - 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/connection/add.go b/cmd/podman/system/connection/add.go new file mode 100644 index 000000000..7522eb190 --- /dev/null +++ b/cmd/podman/system/connection/add.go @@ -0,0 +1,223 @@ +package connection + +import ( + "bytes" + "encoding/json" + "fmt" + "net" + "net/url" + "os" + "os/user" + "regexp" + + "github.com/containers/common/pkg/config" + "github.com/containers/libpod/v2/cmd/podman/registry" + "github.com/containers/libpod/v2/cmd/podman/system" + "github.com/containers/libpod/v2/libpod/define" + "github.com/containers/libpod/v2/pkg/domain/entities" + "github.com/containers/libpod/v2/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 ( + addCmd = &cobra.Command{ + Use: "add [flags] NAME DESTINATION", + Args: cobra.ExactArgs(2), + Short: "Record destination for the Podman service", + Long: `Add destination to podman configuration. + "destination" is of the form [user@]hostname or + an URI of the form ssh://[user@]hostname[:port] +`, + RunE: add, + Example: `podman system connection add laptop server.fubar.com + podman system connection add --identity ~/.ssh/dev_rsa testing ssh://root@server.fubar.com:2222 + podman system connection add --identity ~/.ssh/dev_rsa --port 22 production root@server.fubar.com + `, + } + + cOpts = struct { + Identity string + Port int + UDSPath string + Default bool + }{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: addCmd, + Parent: system.ConnectionCmd, + }) + + flags := addCmd.Flags() + flags.IntVarP(&cOpts.Port, "port", "p", 22, "SSH port number for destination") + flags.StringVar(&cOpts.Identity, "identity", "", "path to SSH identity file") + 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)") + flags.BoolVarP(&cOpts.Default, "default", "d", false, "Set connection to be default") +} + +func add(cmd *cobra.Command, args []string) error { + // Default to ssh: schema if none given + dest := args[1] + if match, err := regexp.Match(schemaPattern, []byte(dest)); err != nil { + return errors.Wrapf(err, "internal regex error %q", schemaPattern) + } else if !match { + dest = "ssh://" + dest + } + + uri, err := url.Parse(dest) + if err != nil { + return errors.Wrapf(err, "failed to parse %q", dest) + } + + if uri.User.Username() == "" { + if uri.User, err = getUserInfo(uri); err != nil { + return err + } + } + + if cmd.Flags().Changed("socket-path") { + uri.Path = cmd.Flag("socket-path").Value.String() + } + + if cmd.Flags().Changed("port") { + 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()) + } + } + + cfg, err := config.ReadCustomConfig() + if err != nil { + return err + } + + if cmd.Flags().Changed("default") { + if cOpts.Default { + cfg.Engine.ActiveService = args[0] + } + } + + dst := config.Destination{ + URI: uri.String(), + } + + if cmd.Flags().Changed("identity") { + dst.Identity = cOpts.Identity + } + + if cfg.Engine.ServiceDestinations == nil { + cfg.Engine.ServiceDestinations = map[string]config.Destination{ + args[0]: dst, + } + } else { + cfg.Engine.ServiceDestinations[args[0]] = dst + } + return cfg.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)) + } + + if cmd.Flags().Changed("identity") { + value := cmd.Flag("identity").Value.String() + auth, err := terminal.PublicKey(value, []byte(passwd)) + if err != nil { + return "", errors.Wrapf(err, "Failed to read identity %q", value) + } + 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/connection/default.go b/cmd/podman/system/connection/default.go new file mode 100644 index 000000000..b85343dc2 --- /dev/null +++ b/cmd/podman/system/connection/default.go @@ -0,0 +1,46 @@ +package connection + +import ( + "fmt" + + "github.com/containers/common/pkg/config" + "github.com/containers/libpod/v2/cmd/podman/registry" + "github.com/containers/libpod/v2/cmd/podman/system" + "github.com/containers/libpod/v2/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + // Skip creating engines since this command will obtain connection information to said engines + dfltCmd = &cobra.Command{ + Use: "default NAME", + Args: cobra.ExactArgs(1), + Short: "Set named destination as default", + Long: `Set named destination as default for the Podman service`, + DisableFlagsInUseLine: true, + RunE: defaultRunE, + Example: `podman system connection default testing`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: dfltCmd, + Parent: system.ConnectionCmd, + }) +} + +func defaultRunE(cmd *cobra.Command, args []string) error { + cfg, err := config.ReadCustomConfig() + if err != nil { + return err + } + + if _, found := cfg.Engine.ServiceDestinations[args[0]]; !found { + return fmt.Errorf("%q destination is not defined. See \"podman system connection add ...\" to create a connection", args[0]) + } + + cfg.Engine.ActiveService = args[0] + return cfg.Write() +} diff --git a/cmd/podman/system/connection/list.go b/cmd/podman/system/connection/list.go new file mode 100644 index 000000000..c0a9087f5 --- /dev/null +++ b/cmd/podman/system/connection/list.go @@ -0,0 +1,84 @@ +package connection + +import ( + "os" + "text/tabwriter" + "text/template" + + "github.com/containers/common/pkg/config" + "github.com/containers/libpod/v2/cmd/podman/registry" + "github.com/containers/libpod/v2/cmd/podman/system" + "github.com/containers/libpod/v2/cmd/podman/validate" + "github.com/containers/libpod/v2/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + listCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Args: validate.NoArgs, + Short: "List destination for the Podman service(s)", + Long: `List destination information for the Podman service(s) in podman configuration`, + DisableFlagsInUseLine: true, + Example: `podman system connection list + podman system connection ls`, + RunE: list, + TraverseChildren: false, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: listCmd, + Parent: system.ConnectionCmd, + }) +} + +type namedDestination struct { + Name string + config.Destination +} + +func list(_ *cobra.Command, _ []string) error { + cfg, err := config.ReadCustomConfig() + if err != nil { + return err + } + + if len(cfg.Engine.ServiceDestinations) == 0 { + return nil + } + + hdrs := []map[string]string{{ + "Identity": "Identity", + "Name": "Name", + "URI": "URI", + }} + + rows := make([]namedDestination, 0) + for k, v := range cfg.Engine.ServiceDestinations { + if k == cfg.Engine.ActiveService { + k += "*" + } + + r := namedDestination{ + Name: k, + Destination: config.Destination{ + Identity: v.Identity, + URI: v.URI, + }, + } + rows = append(rows, r) + } + + // TODO: Allow user to override format + format := "{{range . }}{{.Name}}\t{{.Identity}}\t{{.URI}}\n{{end}}" + tmpl := template.Must(template.New("connection").Parse(format)) + w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + defer w.Flush() + + _ = tmpl.Execute(w, hdrs) + return tmpl.Execute(w, rows) +} diff --git a/cmd/podman/system/connection/remove.go b/cmd/podman/system/connection/remove.go new file mode 100644 index 000000000..a2ca66c8d --- /dev/null +++ b/cmd/podman/system/connection/remove.go @@ -0,0 +1,49 @@ +package connection + +import ( + "github.com/containers/common/pkg/config" + "github.com/containers/libpod/v2/cmd/podman/registry" + "github.com/containers/libpod/v2/cmd/podman/system" + "github.com/containers/libpod/v2/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + // Skip creating engines since this command will obtain connection information to said engines + rmCmd = &cobra.Command{ + Use: "remove NAME", + Args: cobra.ExactArgs(1), + Aliases: []string{"rm"}, + Long: `Delete named destination from podman configuration`, + Short: "Delete named destination", + DisableFlagsInUseLine: true, + RunE: rm, + Example: `podman system connection remove devl + podman system connection rm devl`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: rmCmd, + Parent: system.ConnectionCmd, + }) +} + +func rm(_ *cobra.Command, args []string) error { + cfg, err := config.ReadCustomConfig() + if err != nil { + return err + } + + if cfg.Engine.ServiceDestinations != nil { + delete(cfg.Engine.ServiceDestinations, args[0]) + } + + if cfg.Engine.ActiveService == args[0] { + cfg.Engine.ActiveService = "" + } + + return cfg.Write() +} diff --git a/cmd/podman/system/connection/rename.go b/cmd/podman/system/connection/rename.go new file mode 100644 index 000000000..d6cd55c31 --- /dev/null +++ b/cmd/podman/system/connection/rename.go @@ -0,0 +1,54 @@ +package connection + +import ( + "fmt" + + "github.com/containers/common/pkg/config" + "github.com/containers/libpod/v2/cmd/podman/registry" + "github.com/containers/libpod/v2/cmd/podman/system" + "github.com/containers/libpod/v2/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + // Skip creating engines since this command will obtain connection information to said engines + renameCmd = &cobra.Command{ + Use: "rename OLD NEW", + Aliases: []string{"mv"}, + Args: cobra.ExactArgs(2), + Short: "Rename \"old\" to \"new\"", + Long: `Rename destination for the Podman service from "old" to "new"`, + DisableFlagsInUseLine: true, + RunE: rename, + Example: `podman system connection rename laptop devl, + podman system connection mv laptop devl`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: renameCmd, + Parent: system.ConnectionCmd, + }) +} + +func rename(cmd *cobra.Command, args []string) error { + cfg, err := config.ReadCustomConfig() + if err != nil { + return err + } + + if _, found := cfg.Engine.ServiceDestinations[args[0]]; !found { + return fmt.Errorf("%q destination is not defined. See \"podman system connection add ...\" to create a connection", args[0]) + } + + cfg.Engine.ServiceDestinations[args[1]] = cfg.Engine.ServiceDestinations[args[0]] + delete(cfg.Engine.ServiceDestinations, args[0]) + + if cfg.Engine.ActiveService == args[0] { + cfg.Engine.ActiveService = args[1] + } + + return cfg.Write() +} diff --git a/cmd/podman/system/df.go b/cmd/podman/system/df.go index c2308f0cc..a242c4f66 100644 --- a/cmd/podman/system/df.go +++ b/cmd/podman/system/df.go @@ -2,11 +2,11 @@ package system import ( "fmt" - "html/template" "io" "os" "strings" "text/tabwriter" + "text/template" "time" "github.com/containers/libpod/v2/cmd/podman/registry" diff --git a/cmd/podman/system/events.go b/cmd/podman/system/events.go index 246611c1a..0a46a4042 100644 --- a/cmd/podman/system/events.go +++ b/cmd/podman/system/events.go @@ -3,9 +3,9 @@ package system import ( "bufio" "context" - "html/template" "os" "strings" + "text/template" "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/v2/cmd/podman/registry" diff --git a/cmd/podman/volumes/inspect.go b/cmd/podman/volumes/inspect.go index 9a8f4049b..235137fc7 100644 --- a/cmd/podman/volumes/inspect.go +++ b/cmd/podman/volumes/inspect.go @@ -2,9 +2,9 @@ package volumes import ( "fmt" - "html/template" "os" "strings" + "text/template" "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/v2/cmd/podman/registry" diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go index 9e3a8f77b..804b9f319 100644 --- a/cmd/podman/volumes/list.go +++ b/cmd/podman/volumes/list.go @@ -3,11 +3,11 @@ package volumes import ( "context" "fmt" - "html/template" "io" "os" "strings" "text/tabwriter" + "text/template" "github.com/containers/libpod/v2/cmd/podman/registry" "github.com/containers/libpod/v2/cmd/podman/validate" diff --git a/docs/source/markdown/podman-system-connection-add.1.md b/docs/source/markdown/podman-system-connection-add.1.md new file mode 100644 index 000000000..5059803a2 --- /dev/null +++ b/docs/source/markdown/podman-system-connection-add.1.md @@ -0,0 +1,46 @@ +% podman-system-connection-add(1) + +## NAME +podman\-system\-connection\-add - Record destination for the Podman service + +## SYNOPSIS +**podman system connection add** [*options*] *name* *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. The `ssh-agent` is supported if it is running. + +## OPTIONS + +**-d**, **--default**=*false* + +Make the new destination the default for this user. + +**--identity**=*path* + +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. + +**-p**, **--port**=*port* + +Port for ssh destination. The default value is `22`. + +**--socket-path**=*path* + +Path to the Podman service unix domain socket on the ssh destination host + +## EXAMPLE +``` +$ podman system connection add QA podman.example.com + +$ podman system connection add --identity ~/.ssh/dev_rsa production ssh://root@server.example.com:2222 +``` +## SEE ALSO +podman-system(1) , podman-system-connection(1) , containers.conf(5) + +## HISTORY +June 2020, Originally compiled by Jhon Honce (jhonce at redhat dot com) diff --git a/docs/source/markdown/podman-system-connection-default.1.md b/docs/source/markdown/podman-system-connection-default.1.md new file mode 100644 index 000000000..f324f8c01 --- /dev/null +++ b/docs/source/markdown/podman-system-connection-default.1.md @@ -0,0 +1,20 @@ +% podman-system-connection-default(1) + +## NAME +podman\-system\-connection\-default - Set named destination as default for the Podman service + +## SYNOPSIS +**podman system connection default** *name* + +## DESCRIPTION +Set named ssh destination as default destination for the Podman service. + +## EXAMPLE +``` +$ podman system connection default production +``` +## SEE ALSO +podman-system(1) , podman-system-connection(1) , containers.conf(5) + +## HISTORY +July 2020, Originally compiled by Jhon Honce (jhonce at redhat dot com) diff --git a/docs/source/markdown/podman-system-connection-list.1.md b/docs/source/markdown/podman-system-connection-list.1.md new file mode 100644 index 000000000..f5fb5c8e3 --- /dev/null +++ b/docs/source/markdown/podman-system-connection-list.1.md @@ -0,0 +1,24 @@ +% podman-system-connection-list(1) + +## NAME +podman\-system\-connection\-list - List the destination for the Podman service(s) + +## SYNOPSIS +**podman system connection list** + +**podman system connection ls** + +## DESCRIPTION +List ssh destination(s) for podman service(s). + +## EXAMPLE +``` +$ podman system connection list +Name URI Identity +devl ssh://root@example.com/run/podman/podman.sock ~/.ssh/id_rsa +``` +## SEE ALSO +podman-system(1) , containers.conf(5) + +## HISTORY +July 2020, Originally compiled by Jhon Honce (jhonce at redhat dot com) diff --git a/docs/source/markdown/podman-system-connection-remove.1.md b/docs/source/markdown/podman-system-connection-remove.1.md new file mode 100644 index 000000000..faa767176 --- /dev/null +++ b/docs/source/markdown/podman-system-connection-remove.1.md @@ -0,0 +1,20 @@ +% podman-system-connection-remove(1) + +## NAME +podman\-system\-connection\-remove - Delete named destination + +## SYNOPSIS +**podman system connection remove** *name* + +## DESCRIPTION +Delete named ssh destination. + +## EXAMPLE +``` +$ podman system connection remove production +``` +## SEE ALSO +podman-system(1) , podman-system-connection(1) , containers.conf(5) + +## HISTORY +July 2020, Originally compiled by Jhon Honce (jhonce at redhat dot com) diff --git a/docs/source/markdown/podman-system-connection-rename.1.md b/docs/source/markdown/podman-system-connection-rename.1.md new file mode 100644 index 000000000..819cb697f --- /dev/null +++ b/docs/source/markdown/podman-system-connection-rename.1.md @@ -0,0 +1,20 @@ +% podman-system-connection-rename(1) + +## NAME +podman\-system\-connection\-rename - Rename the destination for Podman service + +## SYNOPSIS +**podman system connection rename** *old* *new* + +## DESCRIPTION +Rename ssh destination from *old* to *new*. + +## EXAMPLE +``` +$ podman system connection rename laptop devel +``` +## SEE ALSO +podman-system(1) , podman-system-connection(1) , containers.conf(5) + +## HISTORY +July 2020, Originally compiled by Jhon Honce (jhonce at redhat dot com) diff --git a/docs/source/markdown/podman-system-connection.1.md b/docs/source/markdown/podman-system-connection.1.md index 66cb656ae..86199c6b9 100644 --- a/docs/source/markdown/podman-system-connection.1.md +++ b/docs/source/markdown/podman-system-connection.1.md @@ -1,43 +1,34 @@ % podman-system-connection(1) ## NAME -podman\-system\-connection - Record ssh destination for remote podman service +podman\-system\-connection - Manage the destination(s) for Podman service(s) -## SYNOPSIS -**podman system connection** [*options*] [*ssh destination*] +## SYNOPSISManage the destination(s) for Podman service(s) +**podman system connection** *subcommand* ## DESCRIPTION -Record ssh destination for remote podman service(s). The ssh destination is given as one of: - - [user@]hostname[:port] - - ssh://[user@]hostname[:port] +Manage the destination(s) for Podman service(s). -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. +The user will be prompted for the ssh login password or key file pass phrase as required. The `ssh-agent` is supported if it is running. -## OPTIONS +## COMMANDS -**--identity**=*path* - -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. - -**-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 +| Command | Man Page | Description | +| ------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------- | +| add | [podman-system-connection-add(1)](podman-system-connection-add.1.md) | Record destination for the Podman service | +| default | [podman-system-connection-default(1)](podman-system-connection-default.1.md) | Set named destination as default for the Podman service | +| list | [podman-system-connection-list(1)](podman-system-connection-list.1.md) | List the destination for the Podman service(s) | +| remove | [podman-system-connection-remove(1)](podman-system-connection-remove.1.md) | Delete named destination | +| rename | [podman-system-connection-rename(1)](podman-system-connection-rename.1.md) | Rename the destination for Podman service | ## EXAMPLE ``` -$ podman system connection podman.fubar.com - -$ podman system connection --identity ~/.ssh/dev_rsa ssh://root@server.fubar.com:2222 - +$ podman system connection list +Name URI Identity +devl ssh://root@example.com/run/podman/podman.sock ~/.ssh/id_rsa ``` ## SEE ALSO -podman-system(1) , containers.conf(5) , connections.conf(5) +podman-system(1) , containers.conf(5) ## HISTORY June 2020, Originally compiled by Jhon Honce (jhonce at redhat dot com) diff --git a/docs/source/markdown/podman-system-reset.1.md b/docs/source/markdown/podman-system-reset.1.md index f290e26d5..3294bac9b 100644 --- a/docs/source/markdown/podman-system-reset.1.md +++ b/docs/source/markdown/podman-system-reset.1.md @@ -7,7 +7,7 @@ podman\-system\-reset - Reset storage back to initial state **podman system reset** [*options*] ## DESCRIPTION -**podman system reset** removes all pods, containers, images and volumes. +**podman system reset** removes all pods, containers, images and volumes. Must be run after changing any of the following values in the `containers.conf` file: `static_dir`, `tmp_dir` or `volume_path`. ## OPTIONS **--force**, **-f** diff --git a/docs/source/markdown/podman-system.1.md b/docs/source/markdown/podman-system.1.md index 1f19fd0b6..9ac73237e 100644 --- a/docs/source/markdown/podman-system.1.md +++ b/docs/source/markdown/podman-system.1.md @@ -11,17 +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. | -| 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 | - +| Command | Man Page | Description | +| ------- | ------------------------------------------------------------ | -------------------------------------------------------------------- | +| connection | [podman-system-connection(1)](podman-system-connection.1.md) | Manage the destination(s) for Podman service(s) | +| 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-system-service(1)](podman-system-service.1.md) | Run an API service | ## SEE ALSO podman(1) diff --git a/docs/source/system.rst b/docs/source/system.rst index 2f2b7ea8f..2d93a1d6d 100644 --- a/docs/source/system.rst +++ b/docs/source/system.rst @@ -10,3 +10,5 @@ System :doc:`prune <markdown/podman-system-prune.1>` Remove unused data :doc:`renumber <markdown/podman-system-renumber.1>` Migrate lock numbers + +:doc:`reset <markdown/podman-system-reset.1>` Reset podman storage diff --git a/hack/xref-helpmsgs-manpages b/hack/xref-helpmsgs-manpages index c1e9dffc4..16b596589 100755 --- a/hack/xref-helpmsgs-manpages +++ b/hack/xref-helpmsgs-manpages @@ -16,6 +16,9 @@ our $VERSION = '0.1'; # For debugging, show data structures using DumpTree($var) #use Data::TreeDumper; $Data::TreeDumper::Displayaddress = 0; +# unbuffer output +$| = 1; + ############################################################################### # BEGIN user-customizable section @@ -266,12 +269,16 @@ sub podman_man { elsif ($section eq 'commands') { # In podman.1.md if ($line =~ /^\|\s*\[podman-(\S+?)\(\d\)\]/) { - $man{$1} = podman_man("podman-$1"); + # $1 will be changed by recursion _*BEFORE*_ left-hand assignment + my $subcmd = $1; + $man{$subcmd} = podman_man("podman-$1"); } # In podman-<subcommand>.1.md elsif ($line =~ /^\|\s+(\S+)\s+\|\s+\[\S+\]\((\S+)\.1\.md\)/) { - $man{$1} = podman_man($2); + # $1 will be changed by recursion _*BEFORE*_ left-hand assignment + my $subcmd = $1; + $man{$subcmd} = podman_man($2); } } diff --git a/libpod/common_test.go b/libpod/common_test.go index dff04af5c..e15e3e7a7 100644 --- a/libpod/common_test.go +++ b/libpod/common_test.go @@ -19,33 +19,41 @@ import ( func getTestContainer(id, name string, manager lock.Manager) (*Container, error) { ctr := &Container{ config: &ContainerConfig{ - ID: id, - Name: name, - RootfsImageID: id, - RootfsImageName: "testimg", - StaticDir: "/does/not/exist/", - LogPath: "/does/not/exist/", - Stdin: true, - Labels: map[string]string{"a": "b", "c": "d"}, - StopSignal: 0, - StopTimeout: 0, - CreatedTime: time.Now(), - Privileged: true, - Mounts: []string{"/does/not/exist"}, - DNSServer: []net.IP{net.ParseIP("192.168.1.1"), net.ParseIP("192.168.2.2")}, - DNSSearch: []string{"example.com", "example.example.com"}, - PortMappings: []ocicni.PortMapping{ - { - HostPort: 80, - ContainerPort: 90, - Protocol: "tcp", - HostIP: "192.168.3.3", - }, - { - HostPort: 100, - ContainerPort: 110, - Protocol: "udp", - HostIP: "192.168.4.4", + ID: id, + Name: name, + ContainerRootFSConfig: ContainerRootFSConfig{ + RootfsImageID: id, + RootfsImageName: "testimg", + StaticDir: "/does/not/exist/", + Mounts: []string{"/does/not/exist"}, + }, + ContainerMiscConfig: ContainerMiscConfig{ + LogPath: "/does/not/exist/", + Stdin: true, + Labels: map[string]string{"a": "b", "c": "d"}, + StopSignal: 0, + StopTimeout: 0, + CreatedTime: time.Now(), + }, + ContainerSecurityConfig: ContainerSecurityConfig{ + Privileged: true, + }, + ContainerNetworkConfig: ContainerNetworkConfig{ + DNSServer: []net.IP{net.ParseIP("192.168.1.1"), net.ParseIP("192.168.2.2")}, + DNSSearch: []string{"example.com", "example.example.com"}, + PortMappings: []ocicni.PortMapping{ + { + HostPort: 80, + ContainerPort: 90, + Protocol: "tcp", + HostIP: "192.168.3.3", + }, + { + HostPort: 100, + ContainerPort: 110, + Protocol: "udp", + HostIP: "192.168.4.4", + }, }, }, }, diff --git a/libpod/container.go b/libpod/container.go index 8a69df685..03358ebdc 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -15,7 +15,6 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/libpod/v2/libpod/define" "github.com/containers/libpod/v2/libpod/lock" - "github.com/containers/libpod/v2/pkg/namespaces" "github.com/containers/libpod/v2/pkg/rootless" "github.com/containers/libpod/v2/utils" "github.com/containers/storage" @@ -215,233 +214,6 @@ type ContainerState struct { containerPlatformState } -// ContainerConfig contains all information that was used to create the -// container. It may not be changed once created. -// It is stored, read-only, on disk -type ContainerConfig struct { - Spec *spec.Spec `json:"spec"` - ID string `json:"id"` - Name string `json:"name"` - // Full ID of the pood the container belongs to - Pod string `json:"pod,omitempty"` - // Namespace the container is in - Namespace string `json:"namespace,omitempty"` - // ID of this container's lock - LockID uint32 `json:"lockID"` - - // CreateCommand is the full command plus arguments of the process the - // container has been created with. - CreateCommand []string `json:"CreateCommand,omitempty"` - - // RawImageName is the raw and unprocessed name of the image when creating - // the container (as specified by the user). May or may not be set. One - // use case to store this data are auto-updates where we need the _exact_ - // name and not some normalized instance of it. - RawImageName string `json:"RawImageName,omitempty"` - - // TODO consider breaking these subsections up into smaller structs - - // UID/GID mappings used by the storage - IDMappings storage.IDMappingOptions `json:"idMappingsOptions,omitempty"` - - // Information on the image used for the root filesystem - RootfsImageID string `json:"rootfsImageID,omitempty"` - RootfsImageName string `json:"rootfsImageName,omitempty"` - // Rootfs to use for the container, this conflicts with RootfsImageID - Rootfs string `json:"rootfs,omitempty"` - // Src path to be mounted on /dev/shm in container. - ShmDir string `json:"ShmDir,omitempty"` - // Size of the container's SHM. - ShmSize int64 `json:"shmSize"` - // Static directory for container content that will persist across - // reboot. - StaticDir string `json:"staticDir"` - // Mounts list contains all additional mounts into the container rootfs. - // These include the SHM mount. - // These must be unmounted before the container's rootfs is unmounted. - Mounts []string `json:"mounts,omitempty"` - // NamedVolumes lists the named volumes to mount into the container. - NamedVolumes []*ContainerNamedVolume `json:"namedVolumes,omitempty"` - // OverlayVolumes lists the overlay volumes to mount into the container. - OverlayVolumes []*ContainerOverlayVolume `json:"overlayVolumes,omitempty"` - - // Security Config - - // Whether the container is privileged - Privileged bool `json:"privileged"` - // SELinux process label for container - ProcessLabel string `json:"ProcessLabel,omitempty"` - // SELinux mount label for root filesystem - MountLabel string `json:"MountLabel,omitempty"` - // LabelOpts are options passed in by the user to setup SELinux labels - LabelOpts []string `json:"labelopts,omitempty"` - // User and group to use in the container - // Can be specified by name or UID/GID - User string `json:"user,omitempty"` - // Additional groups to add - Groups []string `json:"groups,omitempty"` - // AddCurrentUserPasswdEntry indicates that the current user passwd entry - // should be added to the /etc/passwd within the container - AddCurrentUserPasswdEntry bool `json:"addCurrentUserPasswdEntry,omitempty"` - - // Namespace Config - // IDs of container to share namespaces with - // NetNsCtr conflicts with the CreateNetNS bool - // These containers are considered dependencies of the given container - // They must be started before the given container is started - IPCNsCtr string `json:"ipcNsCtr,omitempty"` - MountNsCtr string `json:"mountNsCtr,omitempty"` - NetNsCtr string `json:"netNsCtr,omitempty"` - PIDNsCtr string `json:"pidNsCtr,omitempty"` - UserNsCtr string `json:"userNsCtr,omitempty"` - UTSNsCtr string `json:"utsNsCtr,omitempty"` - CgroupNsCtr string `json:"cgroupNsCtr,omitempty"` - - // IDs of dependency containers. - // These containers must be started before this container is started. - Dependencies []string - - // Network Config - - // CreateNetNS indicates that libpod should create and configure a new - // network namespace for the container. - // This cannot be set if NetNsCtr is also set. - CreateNetNS bool `json:"createNetNS"` - // StaticIP is a static IP to request for the container. - // This cannot be set unless CreateNetNS is set. - // If not set, the container will be dynamically assigned an IP by CNI. - StaticIP net.IP `json:"staticIP"` - // StaticMAC is a static MAC to request for the container. - // This cannot be set unless CreateNetNS is set. - // If not set, the container will be dynamically assigned a MAC by CNI. - StaticMAC net.HardwareAddr `json:"staticMAC"` - // PortMappings are the ports forwarded to the container's network - // namespace - // These are not used unless CreateNetNS is true - PortMappings []ocicni.PortMapping `json:"portMappings,omitempty"` - // UseImageResolvConf indicates that resolv.conf should not be - // bind-mounted inside the container. - // Conflicts with DNSServer, DNSSearch, DNSOption. - UseImageResolvConf bool - // DNS servers to use in container resolv.conf - // Will override servers in host resolv if set - DNSServer []net.IP `json:"dnsServer,omitempty"` - // DNS Search domains to use in container resolv.conf - // Will override search domains in host resolv if set - DNSSearch []string `json:"dnsSearch,omitempty"` - // DNS options to be set in container resolv.conf - // With override options in host resolv if set - DNSOption []string `json:"dnsOption,omitempty"` - // UseImageHosts indicates that /etc/hosts should not be - // bind-mounted inside the container. - // Conflicts with HostAdd. - UseImageHosts bool - // Hosts to add in container - // Will be appended to host's host file - HostAdd []string `json:"hostsAdd,omitempty"` - // Network names (CNI) to add container to. Empty to use default network. - Networks []string `json:"networks,omitempty"` - // Network mode specified for the default network. - NetMode namespaces.NetworkMode `json:"networkMode,omitempty"` - // NetworkOptions are additional options for each network - NetworkOptions map[string][]string `json:"network_options,omitempty"` - - // Image Config - - // UserVolumes contains user-added volume mounts in the container. - // These will not be added to the container's spec, as it is assumed - // they are already present in the spec given to Libpod. Instead, it is - // used when committing containers to generate the VOLUMES field of the - // image that is created, and for triggering some OCI hooks which do not - // fire unless user-added volume mounts are present. - UserVolumes []string `json:"userVolumes,omitempty"` - // Entrypoint is the container's entrypoint. - // It is not used in spec generation, but will be used when the - // container is committed to populate the entrypoint of the new image. - Entrypoint []string `json:"entrypoint,omitempty"` - // Command is the container's command. - // It is not used in spec generation, but will be used when the - // container is committed to populate the command of the new image. - Command []string `json:"command,omitempty"` - - // Misc Options - - // Whether to keep container STDIN open - Stdin bool `json:"stdin,omitempty"` - // Labels is a set of key-value pairs providing additional information - // about a container - Labels map[string]string `json:"labels,omitempty"` - // StopSignal is the signal that will be used to stop the container - StopSignal uint `json:"stopSignal,omitempty"` - // StopTimeout is the signal that will be used to stop the container - StopTimeout uint `json:"stopTimeout,omitempty"` - // Time container was created - CreatedTime time.Time `json:"createdTime"` - // NoCgroups indicates that the container will not create CGroups. It is - // incompatible with CgroupParent. Deprecated in favor of CgroupsMode. - NoCgroups bool `json:"noCgroups,omitempty"` - // CgroupsMode indicates how the container will create cgroups - // (disabled, no-conmon, enabled). It supersedes NoCgroups. - CgroupsMode string `json:"cgroupsMode,omitempty"` - // Cgroup parent of the container - CgroupParent string `json:"cgroupParent"` - // LogPath log location - LogPath string `json:"logPath"` - // LogTag is the tag used for logging - LogTag string `json:"logTag"` - // LogDriver driver for logs - LogDriver string `json:"logDriver"` - // File containing the conmon PID - ConmonPidFile string `json:"conmonPidFile,omitempty"` - // RestartPolicy indicates what action the container will take upon - // exiting naturally. - // Allowed options are "no" (take no action), "on-failure" (restart on - // non-zero exit code, up an a maximum of RestartRetries times), - // and "always" (always restart the container on any exit code). - // The empty string is treated as the default ("no") - RestartPolicy string `json:"restart_policy,omitempty"` - // RestartRetries indicates the number of attempts that will be made to - // restart the container. Used only if RestartPolicy is set to - // "on-failure". - RestartRetries uint `json:"restart_retries,omitempty"` - // TODO log options for log drivers - - // PostConfigureNetNS needed when a user namespace is created by an OCI runtime - // if the network namespace is created before the user namespace it will be - // owned by the wrong user namespace. - PostConfigureNetNS bool `json:"postConfigureNetNS"` - - // OCIRuntime used to create the container - OCIRuntime string `json:"runtime,omitempty"` - - // ExitCommand is the container's exit command. - // This Command will be executed when the container exits - ExitCommand []string `json:"exitCommand,omitempty"` - // IsInfra is a bool indicating whether this container is an infra container used for - // sharing kernel namespaces in a pod - IsInfra bool `json:"pause"` - - // SdNotifyMode tells libpod what to do with a NOTIFY_SOCKET if passed - SdNotifyMode string `json:"sdnotifyMode,omitempty"` - // Systemd tells libpod to setup the container in systemd mode - Systemd bool `json:"systemd"` - - // HealthCheckConfig has the health check command and related timings - HealthCheckConfig *manifest.Schema2HealthConfig `json:"healthcheck"` - - // PreserveFDs is a number of additional file descriptors (in addition - // to 0, 1, 2) that will be passed to the executed process. The total FDs - // passed will be 3 + PreserveFDs. - PreserveFDs uint `json:"preserveFds,omitempty"` - - // Timezone is the timezone inside the container. - // Local means it has the same timezone as the host machine - Timezone string `json:"timezone,omitempty"` - - // Umask is the umask inside the container. - Umask string `json:"umask,omitempty"` -} - // ContainerNamedVolume is a named volume that will be mounted into the // container. Each named volume is a libpod Volume present in the state. type ContainerNamedVolume struct { @@ -1277,10 +1049,13 @@ func (c *Container) AutoRemove() bool { return c.Spec().Annotations[define.InspectAnnotationAutoremove] == define.InspectResponseTrue } +// Timezone returns the timezone configured inside the container. +// Local means it has the same timezone as the host machine func (c *Container) Timezone() string { return c.config.Timezone } +// Umask returns the Umask bits configured inside the container. func (c *Container) Umask() string { return c.config.Umask } diff --git a/libpod/container_config.go b/libpod/container_config.go new file mode 100644 index 000000000..8a98d6341 --- /dev/null +++ b/libpod/container_config.go @@ -0,0 +1,256 @@ +package libpod + +import ( + "net" + "time" + + "github.com/containers/image/v5/manifest" + "github.com/containers/libpod/v2/pkg/namespaces" + "github.com/containers/storage" + "github.com/cri-o/ocicni/pkg/ocicni" + spec "github.com/opencontainers/runtime-spec/specs-go" +) + +// ContainerConfig contains all information that was used to create the +// container. It may not be changed once created. +// It is stored, read-only, on disk +type ContainerConfig struct { + Spec *spec.Spec `json:"spec"` + + ID string `json:"id"` + + Name string `json:"name"` + + // Full ID of the pood the container belongs to + Pod string `json:"pod,omitempty"` + + // Namespace the container is in + Namespace string `json:"namespace,omitempty"` + + // ID of this container's lock + LockID uint32 `json:"lockID"` + + // CreateCommand is the full command plus arguments of the process the + // container has been created with. + CreateCommand []string `json:"CreateCommand,omitempty"` + + // RawImageName is the raw and unprocessed name of the image when creating + // the container (as specified by the user). May or may not be set. One + // use case to store this data are auto-updates where we need the _exact_ + // name and not some normalized instance of it. + RawImageName string `json:"RawImageName,omitempty"` + + // UID/GID mappings used by the storage + IDMappings storage.IDMappingOptions `json:"idMappingsOptions,omitempty"` + + // IDs of dependency containers. + // These containers must be started before this container is started. + Dependencies []string + + // embedded sub-configs + ContainerRootFSConfig + ContainerSecurityConfig + ContainerNameSpaceConfig + ContainerNetworkConfig + ContainerImageConfig + ContainerMiscConfig +} + +// ContainerRootFSConfig is an embedded sub-config providing config info +// about the container's root fs. +type ContainerRootFSConfig struct { + RootfsImageID string `json:"rootfsImageID,omitempty"` + RootfsImageName string `json:"rootfsImageName,omitempty"` + // Rootfs to use for the container, this conflicts with RootfsImageID + Rootfs string `json:"rootfs,omitempty"` + // Src path to be mounted on /dev/shm in container. + ShmDir string `json:"ShmDir,omitempty"` + // Size of the container's SHM. + ShmSize int64 `json:"shmSize"` + // Static directory for container content that will persist across + // reboot. + StaticDir string `json:"staticDir"` + // Mounts list contains all additional mounts into the container rootfs. + // These include the SHM mount. + // These must be unmounted before the container's rootfs is unmounted. + Mounts []string `json:"mounts,omitempty"` + // NamedVolumes lists the named volumes to mount into the container. + NamedVolumes []*ContainerNamedVolume `json:"namedVolumes,omitempty"` + // OverlayVolumes lists the overlay volumes to mount into the container. + OverlayVolumes []*ContainerOverlayVolume `json:"overlayVolumes,omitempty"` +} + +// ContainerSecurityConfig is an embedded sub-config providing security configuration +// to the container. +type ContainerSecurityConfig struct { + // Whether the container is privileged + Privileged bool `json:"privileged"` + // SELinux process label for container + ProcessLabel string `json:"ProcessLabel,omitempty"` + // SELinux mount label for root filesystem + MountLabel string `json:"MountLabel,omitempty"` + // LabelOpts are options passed in by the user to setup SELinux labels + LabelOpts []string `json:"labelopts,omitempty"` + // User and group to use in the container + // Can be specified by name or UID/GID + User string `json:"user,omitempty"` + // Additional groups to add + Groups []string `json:"groups,omitempty"` + // AddCurrentUserPasswdEntry indicates that the current user passwd entry + // should be added to the /etc/passwd within the container + AddCurrentUserPasswdEntry bool `json:"addCurrentUserPasswdEntry,omitempty"` +} + +// ContainerNameSpaceConfig is an embedded sub-config providing +// namespace configuration to the container. +type ContainerNameSpaceConfig struct { + // IDs of container to share namespaces with + // NetNsCtr conflicts with the CreateNetNS bool + // These containers are considered dependencies of the given container + // They must be started before the given container is started + IPCNsCtr string `json:"ipcNsCtr,omitempty"` + MountNsCtr string `json:"mountNsCtr,omitempty"` + NetNsCtr string `json:"netNsCtr,omitempty"` + PIDNsCtr string `json:"pidNsCtr,omitempty"` + UserNsCtr string `json:"userNsCtr,omitempty"` + UTSNsCtr string `json:"utsNsCtr,omitempty"` + CgroupNsCtr string `json:"cgroupNsCtr,omitempty"` +} + +// ContainerNetworkConfig is an embedded sub-config providing network configuration +// to the container. +type ContainerNetworkConfig struct { + // CreateNetNS indicates that libpod should create and configure a new + // network namespace for the container. + // This cannot be set if NetNsCtr is also set. + CreateNetNS bool `json:"createNetNS"` + // StaticIP is a static IP to request for the container. + // This cannot be set unless CreateNetNS is set. + // If not set, the container will be dynamically assigned an IP by CNI. + StaticIP net.IP `json:"staticIP"` + // StaticMAC is a static MAC to request for the container. + // This cannot be set unless CreateNetNS is set. + // If not set, the container will be dynamically assigned a MAC by CNI. + StaticMAC net.HardwareAddr `json:"staticMAC"` + // PortMappings are the ports forwarded to the container's network + // namespace + // These are not used unless CreateNetNS is true + PortMappings []ocicni.PortMapping `json:"portMappings,omitempty"` + // UseImageResolvConf indicates that resolv.conf should not be + // bind-mounted inside the container. + // Conflicts with DNSServer, DNSSearch, DNSOption. + UseImageResolvConf bool + // DNS servers to use in container resolv.conf + // Will override servers in host resolv if set + DNSServer []net.IP `json:"dnsServer,omitempty"` + // DNS Search domains to use in container resolv.conf + // Will override search domains in host resolv if set + DNSSearch []string `json:"dnsSearch,omitempty"` + // DNS options to be set in container resolv.conf + // With override options in host resolv if set + DNSOption []string `json:"dnsOption,omitempty"` + // UseImageHosts indicates that /etc/hosts should not be + // bind-mounted inside the container. + // Conflicts with HostAdd. + UseImageHosts bool + // Hosts to add in container + // Will be appended to host's host file + HostAdd []string `json:"hostsAdd,omitempty"` + // Network names (CNI) to add container to. Empty to use default network. + Networks []string `json:"networks,omitempty"` + // Network mode specified for the default network. + NetMode namespaces.NetworkMode `json:"networkMode,omitempty"` + // NetworkOptions are additional options for each network + NetworkOptions map[string][]string `json:"network_options,omitempty"` +} + +// ContainerImageConfig is an embedded sub-config providing image configuration +// to the container. +type ContainerImageConfig struct { + // UserVolumes contains user-added volume mounts in the container. + // These will not be added to the container's spec, as it is assumed + // they are already present in the spec given to Libpod. Instead, it is + // used when committing containers to generate the VOLUMES field of the + // image that is created, and for triggering some OCI hooks which do not + // fire unless user-added volume mounts are present. + UserVolumes []string `json:"userVolumes,omitempty"` + // Entrypoint is the container's entrypoint. + // It is not used in spec generation, but will be used when the + // container is committed to populate the entrypoint of the new image. + Entrypoint []string `json:"entrypoint,omitempty"` + // Command is the container's command. + // It is not used in spec generation, but will be used when the + // container is committed to populate the command of the new image. + Command []string `json:"command,omitempty"` +} + +// ContainerMiscConfig is an embedded sub-config providing misc configuration +// to the container. +type ContainerMiscConfig struct { + // Whether to keep container STDIN open + Stdin bool `json:"stdin,omitempty"` + // Labels is a set of key-value pairs providing additional information + // about a container + Labels map[string]string `json:"labels,omitempty"` + // StopSignal is the signal that will be used to stop the container + StopSignal uint `json:"stopSignal,omitempty"` + // StopTimeout is the signal that will be used to stop the container + StopTimeout uint `json:"stopTimeout,omitempty"` + // Time container was created + CreatedTime time.Time `json:"createdTime"` + // NoCgroups indicates that the container will not create CGroups. It is + // incompatible with CgroupParent. Deprecated in favor of CgroupsMode. + NoCgroups bool `json:"noCgroups,omitempty"` + // CgroupsMode indicates how the container will create cgroups + // (disabled, no-conmon, enabled). It supersedes NoCgroups. + CgroupsMode string `json:"cgroupsMode,omitempty"` + // Cgroup parent of the container + CgroupParent string `json:"cgroupParent"` + // LogPath log location + LogPath string `json:"logPath"` + // LogTag is the tag used for logging + LogTag string `json:"logTag"` + // LogDriver driver for logs + LogDriver string `json:"logDriver"` + // File containing the conmon PID + ConmonPidFile string `json:"conmonPidFile,omitempty"` + // RestartPolicy indicates what action the container will take upon + // exiting naturally. + // Allowed options are "no" (take no action), "on-failure" (restart on + // non-zero exit code, up an a maximum of RestartRetries times), + // and "always" (always restart the container on any exit code). + // The empty string is treated as the default ("no") + RestartPolicy string `json:"restart_policy,omitempty"` + // RestartRetries indicates the number of attempts that will be made to + // restart the container. Used only if RestartPolicy is set to + // "on-failure". + RestartRetries uint `json:"restart_retries,omitempty"` + // TODO log options for log drivers + // PostConfigureNetNS needed when a user namespace is created by an OCI runtime + // if the network namespace is created before the user namespace it will be + // owned by the wrong user namespace. + PostConfigureNetNS bool `json:"postConfigureNetNS"` + // OCIRuntime used to create the container + OCIRuntime string `json:"runtime,omitempty"` + // ExitCommand is the container's exit command. + // This Command will be executed when the container exits + ExitCommand []string `json:"exitCommand,omitempty"` + // IsInfra is a bool indicating whether this container is an infra container used for + // sharing kernel namespaces in a pod + IsInfra bool `json:"pause"` + // SdNotifyMode tells libpod what to do with a NOTIFY_SOCKET if passed + SdNotifyMode string `json:"sdnotifyMode,omitempty"` + // Systemd tells libpod to setup the container in systemd mode + Systemd bool `json:"systemd"` + // HealthCheckConfig has the health check command and related timings + HealthCheckConfig *manifest.Schema2HealthConfig `json:"healthcheck"` + // PreserveFDs is a number of additional file descriptors (in addition + // to 0, 1, 2) that will be passed to the executed process. The total FDs + // passed will be 3 + PreserveFDs. + PreserveFDs uint `json:"preserveFds,omitempty"` + // Timezone is the timezone inside the container. + // Local means it has the same timezone as the host machine + Timezone string `json:"timezone,omitempty"` + // Umask is the umask inside the container. + Umask string `json:"umask,omitempty"` +} diff --git a/libpod/container_internal_linux_test.go b/libpod/container_internal_linux_test.go index 078cc53a7..41c22fb45 100644 --- a/libpod/container_internal_linux_test.go +++ b/libpod/container_internal_linux_test.go @@ -20,8 +20,10 @@ func TestGenerateUserPasswdEntry(t *testing.T) { c := Container{ config: &ContainerConfig{ - User: "123:456", Spec: &spec.Spec{}, + ContainerSecurityConfig: ContainerSecurityConfig{ + User: "123:456", + }, }, state: &ContainerState{ Mountpoint: "/does/not/exist/tmp/", diff --git a/libpod/container_internal_test.go b/libpod/container_internal_test.go index fdf7c2e20..2b50093b2 100644 --- a/libpod/container_internal_test.go +++ b/libpod/container_internal_test.go @@ -35,7 +35,9 @@ func TestPostDeleteHooks(t *testing.T) { "a": "b", }, }, - StaticDir: dir, // not the bundle, but good enough for this test + ContainerRootFSConfig: ContainerRootFSConfig{ + StaticDir: dir, // not the bundle, but good enough for this test + }, }, state: &ContainerState{ ExtensionStageHooks: map[string][]rspec.Hook{ diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index 4ad6aa862..cbcda474a 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -159,10 +159,10 @@ func makeCreateConfig(ctx context.Context, containerConfig *config.Config, input User: input.User, } pidConfig := createconfig.PidConfig{PidMode: namespaces.PidMode(input.HostConfig.PidMode)} - volumes := make([]string, 0, len(input.Volumes)) - for k := range input.Volumes { - volumes = append(volumes, k) - } + // TODO: We should check that these binds are all listed in the `Volumes` + // key since it doesn't make sense to define a `Binds` element for a + // container path which isn't defined as a volume + volumes := input.HostConfig.Binds // Docker is more flexible about its input where podman throws // away incorrectly formatted variables so we cannot reuse the diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go index 9d5cb5045..8c4ad575b 100644 --- a/pkg/api/handlers/compat/events.go +++ b/pkg/api/handlers/compat/events.go @@ -29,8 +29,14 @@ func filtersFromRequest(r *http.Request) ([]string, error) { compatFilters map[string]map[string]bool filters map[string][]string libpodFilters []string + raw []byte ) - raw := []byte(r.Form.Get("filters")) + + if _, found := r.URL.Query()["filters"]; found { + raw = []byte(r.Form.Get("filters")) + } else { + return []string{}, nil + } // Backwards compat with older versions of Docker. if err := json.Unmarshal(raw, &compatFilters); err == nil { diff --git a/pkg/api/server/docs.go b/pkg/api/server/docs.go index 124c16092..1aaf31117 100644 --- a/pkg/api/server/docs.go +++ b/pkg/api/server/docs.go @@ -1,8 +1,10 @@ -// Package api Provides a container compatible interface. (Experimental) +// Package api Provides a container compatible interface. // -// This documentation describes the HTTP Libpod interface. It is to be considered -// only as experimental as this point. The endpoints, parameters, inputs, and -// return values can all change. +// This documentation describes the Podman v2.0 RESTful API. +// It replaces the Podman v1.0 API and was initially delivered +// along with Podman v2.0. It consists of a Docker-compatible +// API and a Libpod API providing support for Podman’s unique +// features such as pods. // // To start the service and keep it running for 5,000 seconds (-t 0 runs forever): // diff --git a/test/apiv2/01-basic.at b/test/apiv2/01-basic.at index 18ec9bbe8..79dac990a 100644 --- a/test/apiv2/01-basic.at +++ b/test/apiv2/01-basic.at @@ -68,4 +68,8 @@ else _show_ok 0 "Time for ten /info requests" "<= 5 seconds" "$delta_t seconds" fi +# Simple events test (see #7078) +t GET "events?stream=false" 200 +t GET "libpod/events?stream=false" 200 + # vim: filetype=sh diff --git a/test/e2e/system_connection_test.go b/test/e2e/system_connection_test.go new file mode 100644 index 000000000..4c750ee7f --- /dev/null +++ b/test/e2e/system_connection_test.go @@ -0,0 +1,176 @@ +package integration + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/containers/common/pkg/config" + . "github.com/containers/libpod/v2/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" + . "github.com/onsi/gomega/gexec" +) + +var _ = Describe("podman system connection", func() { + ConfPath := struct { + Value string + IsSet bool + }{} + + var ( + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + ConfPath.Value, ConfPath.IsSet = os.LookupEnv("CONTAINERS_CONF") + conf, err := ioutil.TempFile("", "containersconf") + if err != nil { + panic(err) + } + os.Setenv("CONTAINERS_CONF", conf.Name()) + + tempdir, err := CreateTempDirInTempDir() + if err != nil { + panic(err) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() + }) + + AfterEach(func() { + podmanTest.Cleanup() + os.Remove(os.Getenv("CONTAINERS_CONF")) + if ConfPath.IsSet { + os.Setenv("CONTAINERS_CONF", ConfPath.Value) + } else { + os.Unsetenv("CONTAINERS_CONF") + } + + f := CurrentGinkgoTestDescription() + timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) + GinkgoWriter.Write([]byte(timedResult)) + }) + + It("add", func() { + cmd := []string{"system", "connection", "add", + "--default", + "--identity", "~/.ssh/id_rsa", + "QA", + "ssh://root@server.fubar.com:2222/run/podman/podman.sock", + } + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out).Should(Say("")) + + cfg, err := config.ReadCustomConfig() + Expect(err).ShouldNot(HaveOccurred()) + Expect(cfg.Engine.ActiveService).To(Equal("QA")) + Expect(cfg.Engine.ServiceDestinations["QA"]).To(Equal( + config.Destination{ + URI: "ssh://root@server.fubar.com:2222/run/podman/podman.sock", + Identity: "~/.ssh/id_rsa", + }, + )) + + cmd = []string{"system", "connection", "rename", + "QA", + "QE", + } + session = podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + cfg, err = config.ReadCustomConfig() + Expect(err).ShouldNot(HaveOccurred()) + Expect(cfg.Engine.ActiveService).To(Equal("QE")) + Expect(cfg.Engine.ServiceDestinations["QE"]).To(Equal( + config.Destination{ + URI: "ssh://root@server.fubar.com:2222/run/podman/podman.sock", + Identity: "~/.ssh/id_rsa", + }, + )) + }) + + It("remove", func() { + cmd := []string{"system", "connection", "add", + "--default", + "--identity", "~/.ssh/id_rsa", + "QA", + "ssh://root@server.fubar.com:2222/run/podman/podman.sock", + } + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + for i := 0; i < 2; i++ { + cmd = []string{"system", "connection", "remove", "QA"} + session = podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out).Should(Say("")) + + cfg, err := config.ReadCustomConfig() + Expect(err).ShouldNot(HaveOccurred()) + Expect(cfg.Engine.ActiveService).To(BeEmpty()) + Expect(cfg.Engine.ServiceDestinations).To(BeEmpty()) + } + }) + + It("default", func() { + for _, name := range []string{"devl", "qe"} { + cmd := []string{"system", "connection", "add", + "--default", + "--identity", "~/.ssh/id_rsa", + name, + "ssh://root@server.fubar.com:2222/run/podman/podman.sock", + } + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + } + + cmd := []string{"system", "connection", "default", "devl"} + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out).Should(Say("")) + + cfg, err := config.ReadCustomConfig() + Expect(err).ShouldNot(HaveOccurred()) + Expect(cfg.Engine.ActiveService).To(Equal("devl")) + + cmd = []string{"system", "connection", "list"} + session = podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out).Should(Say("Name *Identity *URI")) + }) + + It("failed default", func() { + cmd := []string{"system", "connection", "default", "devl"} + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).ShouldNot(Exit(0)) + Expect(session.Err).Should(Say("destination is not defined")) + }) + + It("failed rename", func() { + cmd := []string{"system", "connection", "rename", "devl", "QE"} + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).ShouldNot(Exit(0)) + Expect(session.Err).Should(Say("destination is not defined")) + }) + + It("empty list", func() { + cmd := []string{"system", "connection", "list"} + session := podmanTest.Podman(cmd) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.Out).Should(Say("")) + Expect(session.Err).Should(Say("")) + }) +}) diff --git a/transfer.md b/transfer.md index a9cc8a756..9aa271c37 100644 --- a/transfer.md +++ b/transfer.md @@ -54,6 +54,10 @@ There are other equivalents for these tools | `docker load` | [`podman load`](./docs/source/markdown/podman-load.1.md) | | `docker login` | [`podman login`](./docs/source/markdown/podman-login.1.md) | | `docker logout` | [`podman logout`](./docs/source/markdown/podman-logout.1.md) | +| `docker network create` | [`podman network create`](./docs/source/markdown/podman-network-create.1.md) | +| `docker network inspect` | [`podman network inspect`](./docs/source/markdown/podman-network-inspect.1.md) | +| `docker network ls` | [`podman network ls`](./docs/source/markdown/podman-network-ls.1.md) | +| `docker network rm` | [`podman network rm`](./docs.source/markdown/podman-network-rm.1.md) | | `docker pause` | [`podman pause`](./docs/source/markdown/podman-pause.1.md) | | `docker ps` | [`podman ps`](./docs/source/markdown/podman-ps.1.md) | | `docker pull` | [`podman pull`](./docs/source/markdown/podman-pull.1.md) | @@ -93,14 +97,12 @@ Those Docker commands currently do not have equivalents in `podman`: | :--- | :--- | | `docker container update` | podman does not support altering running containers. We recommend recreating containers with the correct arguments.| | `docker container rename` | podman does not support `container rename` - or the `rename` shorthand. We recommend using `podman rm` and `podman create` to create a container with a specific name.| -| `docker network` || | `docker node` || | `docker plugin` | podman does not support plugins. We recommend you use alternative OCI Runtimes or OCI Runtime Hooks to alter behavior of podman.| | `docker secret` || | `docker service` || | `docker stack` || | `docker swarm` | podman does not support swarm. We support Kubernetes for orchestration using [CRI-O](https://github.com/cri-o/cri-o).| -| `docker volume` | podman currently supports file volumes. Future enhancement planned to support Docker Volumes Plugins ## Missing commands in Docker @@ -134,5 +136,4 @@ The following podman commands do not have a Docker equivalent: * [`podman pod stop`](./docs/source/markdown/podman-pod-stop.1.md) * [`podman pod top`](./docs/source/markdown/podman-pod-top.1.md) * [`podman pod unpause`](./docs/source/markdown/podman-pod-unpause.1.md) -* [`podman varlink`](./docs/source/markdown/podman-varlink.1.md) * [`podman umount`](./docs/source/markdown/podman-umount.1.md) |