Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor podman system connection #6938

Merged
merged 1 commit into from
Jul 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/podman/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
31 changes: 24 additions & 7 deletions cmd/podman/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm feeling pretty squicked out by the interchange of URI and URL. I think that will lead to maintenance headaches. From my (limited) understanding, I believe URL (l as in Lima) is the correct term. Would you consider replacing the code instances of uri (i as India) to url for consistency?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes let's stick with URL

Copy link
Member Author

@jwhonce jwhonce Jul 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rhatdan @edsantiago

uri was chosen for a reason. url is a golang package name. There is a lot of code that uses the url package to process the "uri" variable. We would be forced to alias the package name or make up longer names which I deemed to be a large maintenance headache. I attempt to use uri for internals and URL for user facing so everyone is happy.

As a url is a uri this naming simplifies the coding and is correct naming.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would calling it path be a good compromise? It's less specific as to what exact type of path it is, and only one character longer.

Copy link
Member Author

@jwhonce jwhonce Jul 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mheon Path is an element of the url/uri, so that would be confusing to see path.Path.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we call everything URI and drop URL, if we want to still support the --url for backwards compatibility just make it an alias.

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\")")
Expand Down Expand Up @@ -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
}
201 changes: 13 additions & 188 deletions cmd/podman/system/connection.go
Original file line number Diff line number Diff line change
@@ -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://[email protected]:2222
podman system connection --identity ~/.ssh/dev_rsa --port 22 [email protected]`,
}

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