[WIP] Refactor podman system connection

* Add support to manage multiple connections
  * Add connection
  * Remove connection
  * Rename connection
  * Set connection as default
  * Add markdown/man pages
* Fix recursion in hack/xref-helpmsgs-manpages

Signed-off-by: Jhon Honce <jhonce@redhat.com>
This commit is contained in:
Jhon Honce
2020-07-21 10:36:44 -07:00
parent 1aac197f79
commit 964d3300c6
17 changed files with 836 additions and 234 deletions

View File

@ -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"

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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);
}
}

View File

@ -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(""))
})
})