From 34f4a892e4056dd660db85db6ee8baa0470dd6f2 Mon Sep 17 00:00:00 2001 From: Qi Wang Date: Thu, 23 Jul 2020 15:54:12 -0400 Subject: podman save use named pipe podman save uses named pipe as output path, not directly using /dev/stdout. fix #7017 Signed-off-by: Qi Wang Signed-off-by: Matt Heon --- cmd/podman/images/save.go | 31 +++++++++++++++++----- cmd/podman/images/utils_linux.go | 47 ++++++++++++++++++++++++++++++++++ cmd/podman/images/utils_unsupported.go | 7 +++++ 3 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 cmd/podman/images/utils_linux.go create mode 100644 cmd/podman/images/utils_unsupported.go (limited to 'cmd') diff --git a/cmd/podman/images/save.go b/cmd/podman/images/save.go index f84d97b17..00eb9f1e6 100644 --- a/cmd/podman/images/save.go +++ b/cmd/podman/images/save.go @@ -5,10 +5,9 @@ import ( "os" "strings" - "github.com/containers/libpod/v2/libpod/define" - "github.com/containers/libpod/v2/cmd/podman/parse" "github.com/containers/libpod/v2/cmd/podman/registry" + "github.com/containers/libpod/v2/libpod/define" "github.com/containers/libpod/v2/pkg/domain/entities" "github.com/containers/libpod/v2/pkg/util" "github.com/pkg/errors" @@ -83,9 +82,10 @@ func saveFlags(flags *pflag.FlagSet) { } -func save(cmd *cobra.Command, args []string) error { +func save(cmd *cobra.Command, args []string) (finalErr error) { var ( - tags []string + tags []string + succeeded = false ) if cmd.Flag("compress").Changed && (saveOpts.Format != define.OCIManifestDir && saveOpts.Format != define.V2s2ManifestDir && saveOpts.Format == "") { return errors.Errorf("--compress can only be set when --format is either 'oci-dir' or 'docker-dir'") @@ -95,7 +95,22 @@ func save(cmd *cobra.Command, args []string) error { if terminal.IsTerminal(int(fi.Fd())) { return errors.Errorf("refusing to save to terminal. Use -o flag or redirect") } - saveOpts.Output = "/dev/stdout" + pipePath, cleanup, err := setupPipe() + if err != nil { + return err + } + if cleanup != nil { + defer func() { + errc := cleanup() + if succeeded { + writeErr := <-errc + if writeErr != nil && finalErr == nil { + finalErr = writeErr + } + } + }() + } + saveOpts.Output = pipePath } if err := parse.ValidateFileName(saveOpts.Output); err != nil { return err @@ -103,5 +118,9 @@ func save(cmd *cobra.Command, args []string) error { if len(args) > 1 { tags = args[1:] } - return registry.ImageEngine().Save(context.Background(), args[0], tags, saveOpts) + err := registry.ImageEngine().Save(context.Background(), args[0], tags, saveOpts) + if err == nil { + succeeded = true + } + return err } diff --git a/cmd/podman/images/utils_linux.go b/cmd/podman/images/utils_linux.go new file mode 100644 index 000000000..5521abab4 --- /dev/null +++ b/cmd/podman/images/utils_linux.go @@ -0,0 +1,47 @@ +package images + +import ( + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +// setupPipe for fixing https://github.com/containers/podman/issues/7017 +// uses named pipe since containers/image EvalSymlinks fails with /dev/stdout +// the caller should use the returned function to clean up the pipeDir +func setupPipe() (string, func() <-chan error, error) { + errc := make(chan error) + pipeDir, err := ioutil.TempDir(os.TempDir(), "pipeDir") + if err != nil { + return "", nil, err + } + pipePath := filepath.Join(pipeDir, "saveio") + err = unix.Mkfifo(pipePath, 0600) + if err != nil { + if e := os.RemoveAll(pipeDir); e != nil { + logrus.Errorf("error removing named pipe: %q", e) + } + return "", nil, errors.Wrapf(err, "error creating named pipe") + } + go func() { + fpipe, err := os.Open(pipePath) + if err != nil { + errc <- err + return + } + _, err = io.Copy(os.Stdout, fpipe) + fpipe.Close() + errc <- err + }() + return pipePath, func() <-chan error { + if e := os.RemoveAll(pipeDir); e != nil { + logrus.Errorf("error removing named pipe: %q", e) + } + return errc + }, nil +} diff --git a/cmd/podman/images/utils_unsupported.go b/cmd/podman/images/utils_unsupported.go new file mode 100644 index 000000000..69d1df786 --- /dev/null +++ b/cmd/podman/images/utils_unsupported.go @@ -0,0 +1,7 @@ +// +build !linux + +package images + +func setupPipe() (string, func() <-chan error, error) { + return "/dev/stdout", nil, nil +} -- cgit v1.2.3-54-g00ecf From 32f0c8f624e0400a6decf219d6fe889f12272729 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Fri, 7 Aug 2020 13:57:02 -0400 Subject: Do not use image CMD if user gave ENTRYPOINT This matches Docker behavior, and seems to make sense - the CMD may have been specific to the original entrypoint and probably does not make sense if it was changed. While we're in here, greatly simplify the logic for populating the SpecGen's Command. We create the full command when making the OCI spec, so the client should not be doing any more than setting it to the Command the user passed in, and completely ignoring ENTRYPOINT. Fixes #7115 Signed-off-by: Matthew Heon --- cmd/podman/common/specgen.go | 18 +----------------- pkg/specgen/generate/oci.go | 4 +++- test/e2e/run_test.go | 11 +++++------ 3 files changed, 9 insertions(+), 24 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 2333f2f7e..48a2069ff 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -387,8 +387,6 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.Annotations = annotations s.WorkDir = c.Workdir - userCommand := []string{} - var command []string if c.Entrypoint != nil { entrypoint := []string{} if ep := *c.Entrypoint; len(ep) > 0 { @@ -398,27 +396,13 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string } } s.Entrypoint = entrypoint - // Build the command - // If we have an entry point, it goes first - command = entrypoint } // Include the command used to create the container. s.ContainerCreateCommand = os.Args if len(inputCommand) > 0 { - // User command overrides data CMD - command = append(command, inputCommand...) - userCommand = append(userCommand, inputCommand...) - } - - switch { - case len(inputCommand) > 0: - s.Command = userCommand - case c.Entrypoint != nil: - s.Command = []string{} - default: - s.Command = command + s.Command = inputCommand } // SHM Size diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index f279aac1c..f1c9f2a1a 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -96,8 +96,10 @@ func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image finalCommand = append(finalCommand, entrypoint...) + // Only use image command if the user did not manually set an + // entrypoint. command := s.Command - if command == nil && img != nil { + if command == nil && img != nil && s.Entrypoint == nil { newCmd, err := img.Cmd(ctx) if err != nil { return nil, err diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index ef275b32e..681498ea1 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -1061,13 +1061,12 @@ USER mail` Expect(session.OutputToString()).To(ContainSubstring(limit)) }) - It("podman run makes entrypoint from image", func() { - dockerfile := `FROM busybox -WORKDIR /madethis` - podmanTest.BuildImage(dockerfile, "test", "false") - session := podmanTest.Podman([]string{"run", "--rm", "test", "pwd"}) + It("podman run --entrypoint does not use image command", func() { + session := podmanTest.Podman([]string{"run", "--entrypoint", "/bin/echo", ALPINE}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(session.OutputToString()).To(ContainSubstring("/madethis")) + // We can't guarantee the output is completely empty, some + // nonprintables seem to work their way in. + Expect(session.OutputToString()).To(Not(ContainSubstring("/bin/sh"))) }) }) -- cgit v1.2.3-54-g00ecf From 35d2db807239eb193edd0d775a95aadb3279f01f Mon Sep 17 00:00:00 2001 From: Jhon Honce Date: Wed, 5 Aug 2020 15:10:12 -0700 Subject: Default .Repository and .Tag values to Refactor the processing of Repository and Tag fields to default to when printing via --format flag. Previously, the default format would print but --format {{.Tag}} would not in some cases. Fixes #7123 Signed-off-by: Jhon Honce --- cmd/podman/images/list.go | 18 +++++++++++++----- test/system/120-load.bats | 2 -- 2 files changed, 13 insertions(+), 7 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go index ea88b519b..60af5c847 100644 --- a/cmd/podman/images/list.go +++ b/cmd/podman/images/list.go @@ -195,6 +195,7 @@ func sortImages(imageS []*entities.ImageSummary) ([]imageReporter, error) { } else { h.ImageSummary = *e h.Repository = "" + h.Tag = "" imgs = append(imgs, h) } listFlag.readOnly = e.IsReadOnly() @@ -205,27 +206,34 @@ func sortImages(imageS []*entities.ImageSummary) ([]imageReporter, error) { } func tokenRepoTag(ref string) (string, string, error) { - if ref == ":" { return "", "", nil } repo, err := reference.Parse(ref) if err != nil { - return "", "", err + return "", "", err } named, ok := repo.(reference.Named) if !ok { - return ref, "", nil + return ref, "", nil + } + name := named.Name() + if name == "" { + name = "" } tagged, ok := repo.(reference.Tagged) if !ok { - return named.Name(), "", nil + return name, "", nil + } + tag := tagged.Tag() + if tag == "" { + tag = "" } - return named.Name(), tagged.Tag(), nil + return name, tag, nil } diff --git a/test/system/120-load.bats b/test/system/120-load.bats index ec959ca73..310ee55de 100644 --- a/test/system/120-load.bats +++ b/test/system/120-load.bats @@ -38,8 +38,6 @@ verify_iid_and_name() { @test "podman load - by image ID" { - skip_if_remote "FIXME: pending #7123" - # FIXME: how to build a simple archive instead? get_iid_and_name -- cgit v1.2.3-54-g00ecf From 23348e7f313a570c513772a88db7f2741c8242ba Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Fri, 31 Jul 2020 13:52:14 -0400 Subject: Ensure DefaultEnvVariables is used in Specgen When we rewrote Podman's pkg/spec, one of the things that was lost was our use of a set of default environment variables, that ensure all containers have at least $PATH and $TERM set. While we're in the process of re-adding it, change it from a variable to a function, so we can ensure the Join function does not overwrite it and corrupt the defaults. Signed-off-by: Matthew Heon --- cmd/podman/common/specgen.go | 5 ++--- pkg/env/env.go | 14 ++++++++------ pkg/spec/spec.go | 4 ++-- pkg/specgen/generate/container.go | 9 +++++++++ pkg/specgen/generate/oci.go | 1 - 5 files changed, 21 insertions(+), 12 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 48a2069ff..2074ed4fa 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -308,9 +308,8 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string // // Precedence order (higher index wins): // 1) env-host, 2) image data, 3) env-file, 4) env - env := map[string]string{ - "container": "podman", - } + env := make(map[string]string) + env["container"] = "podman" // First transform the os env into a map. We need it for the labels later in // any case. diff --git a/pkg/env/env.go b/pkg/env/env.go index a16007a50..0d55e5560 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -12,14 +12,16 @@ import ( "github.com/pkg/errors" ) -// DefaultEnvVariables sets $PATH and $TERM. -var DefaultEnvVariables = map[string]string{ - "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "TERM": "xterm", -} - const whiteSpaces = " \t" +// DefaultEnvVariables returns a default environment, with $PATH and $TERM set. +func DefaultEnvVariables() map[string]string { + return map[string]string{ + "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "TERM": "xterm", + } +} + // Slice transforms the specified map of environment variables into a // slice. If a value is non-empty, the key and value are joined with '='. func Slice(m map[string]string) []string { diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index b974772d5..0dbdc76bb 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -321,13 +321,13 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM // config. var defaultEnv map[string]string if runtimeConfig == nil { - defaultEnv = env.DefaultEnvVariables + defaultEnv = env.DefaultEnvVariables() } else { defaultEnv, err = env.ParseSlice(runtimeConfig.Containers.Env) if err != nil { return nil, errors.Wrap(err, "Env fields in containers.conf failed ot parse") } - defaultEnv = env.Join(env.DefaultEnvVariables, defaultEnv) + defaultEnv = env.Join(env.DefaultEnvVariables(), defaultEnv) } if err := addRlimits(config, &g); err != nil { diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index 59fee80f1..06ffa3df6 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -86,6 +86,15 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat s.Env = envLib.Join(envLib.Join(defaultEnvs, envs), s.Env) + // Ensure that default environment variables are populated. + // Container must have PATH and TERM set, even if nothing else set them. + baseEnv := envLib.DefaultEnvVariables() + for k, v := range baseEnv { + if _, ok := s.Env[k]; !ok { + s.Env[k] = v + } + } + // Labels and Annotations annotations := make(map[string]string) if newImage != nil { diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index f1c9f2a1a..aefc7204c 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -260,7 +260,6 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt for key, val := range s.Annotations { g.AddAnnotation(key, val) } - g.AddProcessEnv("container", "podman") g.Config.Linux.Resources = s.ResourceLimits -- cgit v1.2.3-54-g00ecf From b216b3391c3b58dc876d8cff2deb969b1c5d8f98 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Tue, 18 Aug 2020 16:40:36 -0400 Subject: Revert "remove podman system connection" This reverts commit 66e1626282fab661ac12a354f70b3b2221c69d7c. We are reenabling podman-system-connection. Signed-off-by: Matthew Heon --- cmd/podman/system/connection.go | 208 +++++++++++++++++++++ docs/source/markdown/podman-system-connection.1.md | 37 ++++ docs/source/markdown/podman-system.1.md | 1 + 3 files changed, 246 insertions(+) create mode 100644 cmd/podman/system/connection.go create mode 100644 docs/source/markdown/podman-system-connection.1.md (limited to 'cmd') diff --git a/cmd/podman/system/connection.go b/cmd/podman/system/connection.go new file mode 100644 index 000000000..bdb113ea3 --- /dev/null +++ b/cmd/podman/system/connection.go @@ -0,0 +1,208 @@ +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/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 + noOp = func(cmd *cobra.Command, args []string) error { + return nil + } + connectionCmd = &cobra.Command{ + Use: "connection [flags] destination", + Args: cobra.ExactArgs(1), + Long: `Store ssh destination information in podman configuration. + "destination" is of the form [user@]hostname or + an URI of the form ssh://[user@]hostname[:port] +`, + Short: "Record remote ssh destination", + PersistentPreRunE: noOp, + PersistentPostRunE: noOp, + TraverseChildren: false, + RunE: connection, + Example: `podman system connection server.fubar.com + podman system connection --identity ~/.ssh/dev_rsa ssh://root@server.fubar.com:2222 + podman system connection --identity ~/.ssh/dev_rsa --port 22 root@server.fubar.com`, + } + + cOpts = struct { + Identity string + Port int + UDSPath string + }{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: connectionCmd, + Parent: systemCmd, + }) + + flags := connectionCmd.Flags() + flags.IntVarP(&cOpts.Port, "port", "p", 22, "port number for destination") + flags.StringVar(&cOpts.UDSPath, "socket-path", "", "path to podman socket on remote host. (default '/run/podman/podman.sock' or '/run/user/{uid}/podman/podman.sock)") +} + +func connection(cmd *cobra.Command, args []string) error { + // Default to ssh: schema if none given + dest := []byte(args[0]) + if match, err := regexp.Match(schemaPattern, dest); err != nil { + return errors.Wrapf(err, "internal regex error %q", schemaPattern) + } else if !match { + dest = append([]byte("ssh://"), dest...) + } + + uri, err := url.Parse(string(dest)) + if err != nil { + return errors.Wrapf(err, "failed to parse %q", string(dest)) + } + + if uri.User.Username() == "" { + if uri.User, err = getUserInfo(uri); err != nil { + return err + } + } + + if cmd.Flag("socket-path").Changed { + uri.Path = cmd.Flag("socket-path").Value.String() + } + + if cmd.Flag("port").Changed { + uri.Host = net.JoinHostPort(uri.Hostname(), cmd.Flag("port").Value.String()) + } + + if uri.Port() == "" { + uri.Host = net.JoinHostPort(uri.Hostname(), cmd.Flag("port").DefValue) + } + + if uri.Path == "" { + if uri.Path, err = getUDS(cmd, uri); err != nil { + return errors.Wrapf(err, "failed to connect to %q", uri.String()) + } + } + + custom, err := config.ReadCustomConfig() + if err != nil { + return err + } + + if cmd.Flag("identity").Changed { + custom.Engine.RemoteIdentity = cOpts.Identity + } + + custom.Engine.RemoteURI = uri.String() + return custom.Write() +} + +func getUserInfo(uri *url.URL) (*url.Userinfo, error) { + var ( + usr *user.User + err error + ) + if u, found := os.LookupEnv("_CONTAINERS_ROOTLESS_UID"); found { + usr, err = user.LookupId(u) + if err != nil { + return nil, errors.Wrapf(err, "failed to find user %q", u) + } + } else { + usr, err = user.Current() + if err != nil { + return nil, errors.Wrapf(err, "failed to obtain current user") + } + } + + pw, set := uri.User.Password() + if set { + return url.UserPassword(usr.Username, pw), nil + } + return url.User(usr.Username), nil +} + +func getUDS(cmd *cobra.Command, uri *url.URL) (string, error) { + var authMethods []ssh.AuthMethod + passwd, set := uri.User.Password() + if set { + authMethods = append(authMethods, ssh.Password(passwd)) + } + + ident := cmd.Flag("identity") + if ident.Changed { + auth, err := terminal.PublicKey(ident.Value.String(), []byte(passwd)) + if err != nil { + return "", errors.Wrapf(err, "Failed to read identity %q", ident.Value.String()) + } + authMethods = append(authMethods, auth) + } + + if sock, found := os.LookupEnv("SSH_AUTH_SOCK"); found { + logrus.Debugf("Found SSH_AUTH_SOCK %q, ssh-agent signer enabled", sock) + + c, err := net.Dial("unix", sock) + if err != nil { + return "", err + } + a := agent.NewClient(c) + authMethods = append(authMethods, ssh.PublicKeysCallback(a.Signers)) + } + + config := &ssh.ClientConfig{ + User: uri.User.Username(), + Auth: authMethods, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + dial, err := ssh.Dial("tcp", uri.Host, config) + if err != nil { + return "", errors.Wrapf(err, "failed to connect to %q", uri.Host) + } + defer dial.Close() + + session, err := dial.NewSession() + if err != nil { + return "", errors.Wrapf(err, "failed to create new ssh session on %q", uri.Host) + } + defer session.Close() + + // Override podman binary for testing etc + podman := "podman" + if v, found := os.LookupEnv("PODMAN_BINARY"); found { + podman = v + } + run := podman + " info --format=json" + + var buffer bytes.Buffer + session.Stdout = &buffer + if err := session.Run(run); err != nil { + return "", errors.Wrapf(err, "failed to run %q", run) + } + + var info define.Info + if err := json.Unmarshal(buffer.Bytes(), &info); err != nil { + return "", errors.Wrapf(err, "failed to parse 'podman info' results") + } + + if info.Host.RemoteSocket == nil || len(info.Host.RemoteSocket.Path) == 0 { + return "", fmt.Errorf("remote podman %q failed to report its UDS socket", uri.Host) + } + return info.Host.RemoteSocket.Path, nil +} diff --git a/docs/source/markdown/podman-system-connection.1.md b/docs/source/markdown/podman-system-connection.1.md new file mode 100644 index 000000000..ed73980d6 --- /dev/null +++ b/docs/source/markdown/podman-system-connection.1.md @@ -0,0 +1,37 @@ +% podman-system-connection(1) + +## NAME +podman\-system\-connection - Record ssh destination for remote podman service + +## SYNOPSIS +**podman system connection** [*options*] [*ssh destination*] + +## DESCRIPTION +Record ssh destination for remote podman service(s). The ssh destination is given as one of: + - [user@]hostname[:port] + - ssh://[user@]hostname[:port] + +The user will be prompted for the remote ssh login password or key file pass phrase as required. `ssh-agent` is supported if it is running. + +## OPTIONS + +**-p**, **--port**=*port* + +Port for ssh destination. The default value is `22`. + +**--socket-path**=*path* + +Path to podman service unix domain socket on the ssh destination host + +## EXAMPLE +``` +$ podman system connection podman.fubar.com + +$ podman system connection --identity ~/.ssh/dev_rsa ssh://root@server.fubar.com:2222 + +``` +## SEE ALSO +podman-system(1) , containers.conf(5) , connections.conf(5) + +## HISTORY +June 2020, Originally compiled by Jhon Honce (jhonce at redhat dot com) diff --git a/docs/source/markdown/podman-system.1.md b/docs/source/markdown/podman-system.1.md index 814462ed6..1f19fd0b6 100644 --- a/docs/source/markdown/podman-system.1.md +++ b/docs/source/markdown/podman-system.1.md @@ -14,6 +14,7 @@ The system command allows you to manage the podman systems | 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. | -- cgit v1.2.3-54-g00ecf From 7c13b8c12f0382c921d10ba917c558d3ed6c0fb4 Mon Sep 17 00:00:00 2001 From: Jhon Honce Date: Wed, 15 Jul 2020 08:43:29 -0700 Subject: Fix `podman system connection` panic Signed-off-by: Jhon Honce --- cmd/podman/system/connection.go | 3 ++- docs/source/markdown/podman-system-connection.1.md | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'cmd') diff --git a/cmd/podman/system/connection.go b/cmd/podman/system/connection.go index bdb113ea3..bf015191f 100644 --- a/cmd/podman/system/connection.go +++ b/cmd/podman/system/connection.go @@ -60,7 +60,8 @@ func init() { }) flags := connectionCmd.Flags() - flags.IntVarP(&cOpts.Port, "port", "p", 22, "port number for destination") + 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)") } diff --git a/docs/source/markdown/podman-system-connection.1.md b/docs/source/markdown/podman-system-connection.1.md index ed73980d6..66cb656ae 100644 --- a/docs/source/markdown/podman-system-connection.1.md +++ b/docs/source/markdown/podman-system-connection.1.md @@ -15,6 +15,12 @@ The user will be prompted for the remote ssh login password or key file pass phr ## OPTIONS +**--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`. -- cgit v1.2.3-54-g00ecf From ee956b04b05eed25d3aec967d8c68e7d1418fa09 Mon Sep 17 00:00:00 2001 From: Jhon Honce Date: Tue, 21 Jul 2020 10:36:44 -0700 Subject: [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 Signed-off-by: Matt Heon --- cmd/podman/main.go | 1 + cmd/podman/root.go | 31 ++- cmd/podman/system/connection.go | 201 ++----------------- cmd/podman/system/connection/add.go | 223 +++++++++++++++++++++ cmd/podman/system/connection/default.go | 46 +++++ cmd/podman/system/connection/list.go | 84 ++++++++ cmd/podman/system/connection/remove.go | 49 +++++ cmd/podman/system/connection/rename.go | 54 +++++ .../markdown/podman-system-connection-add.1.md | 46 +++++ .../markdown/podman-system-connection-default.1.md | 20 ++ .../markdown/podman-system-connection-list.1.md | 24 +++ .../markdown/podman-system-connection-remove.1.md | 20 ++ .../markdown/podman-system-connection-rename.1.md | 20 ++ docs/source/markdown/podman-system-connection.1.md | 43 ++-- docs/source/markdown/podman-system.1.md | 21 +- hack/xref-helpmsgs-manpages | 11 +- test/e2e/system_connection_test.go | 176 ++++++++++++++++ 17 files changed, 836 insertions(+), 234 deletions(-) create mode 100644 cmd/podman/system/connection/add.go create mode 100644 cmd/podman/system/connection/default.go create mode 100644 cmd/podman/system/connection/list.go create mode 100644 cmd/podman/system/connection/remove.go create mode 100644 cmd/podman/system/connection/rename.go create mode 100644 docs/source/markdown/podman-system-connection-add.1.md create mode 100644 docs/source/markdown/podman-system-connection-default.1.md create mode 100644 docs/source/markdown/podman-system-connection-list.1.md create mode 100644 docs/source/markdown/podman-system-connection-remove.1.md create mode 100644 docs/source/markdown/podman-system-connection-rename.1.md create mode 100644 test/e2e/system_connection_test.go (limited to 'cmd') 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/root.go b/cmd/podman/root.go index b2c9f9c2c..7895a5f64 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -221,16 +221,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\")") @@ -277,3 +273,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 bf015191f..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/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.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/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-.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/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("")) + }) +}) -- cgit v1.2.3-54-g00ecf From 14379d6dbd6caaf331a86a994c9127a71918d6f4 Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Tue, 4 Aug 2020 13:26:06 -0500 Subject: remove --latest for all remote commands instead of hiding the latest options for podman-remote or catching an error if podman --remote -l is used, we no longer add the latest option to any remote command. podman will error with a "unknown flag" option. Fixes: #7127 Signed-off-by: Brent Baude --- cmd/podman/root.go | 10 ---------- cmd/podman/validate/args.go | 32 ++++++++++++++++++++------------ cmd/podman/validate/latest.go | 7 +++---- 3 files changed, 23 insertions(+), 26 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 7895a5f64..1165f1b84 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -6,7 +6,6 @@ import ( "path" "runtime" "runtime/pprof" - "strconv" "strings" "github.com/containers/common/pkg/config" @@ -112,15 +111,6 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { cfg := registry.PodmanConfig() - // Validate --remote and --latest not given on same command - latest := cmd.Flags().Lookup("latest") - if latest != nil { - value, _ := strconv.ParseBool(latest.Value.String()) - if cfg.Remote && value { - return errors.Errorf("For %s \"--remote\" and \"--latest\", are mutually exclusive flags", cmd.CommandPath()) - } - } - // Prep the engines if _, err := registry.NewImageEngine(cmd, args); err != nil { return err diff --git a/cmd/podman/validate/args.go b/cmd/podman/validate/args.go index a33f47959..aacb41e69 100644 --- a/cmd/podman/validate/args.go +++ b/cmd/podman/validate/args.go @@ -4,6 +4,7 @@ import ( "fmt" "strconv" + "github.com/containers/podman/v2/cmd/podman/registry" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -47,17 +48,20 @@ func IDOrLatestArgs(cmd *cobra.Command, args []string) error { // CheckAllLatestAndCIDFile checks that --all and --latest are used correctly. // If cidfile is set, also check for the --cidfile flag. func CheckAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool, cidfile bool) error { + var specifiedLatest bool argLen := len(args) - if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { - if !cidfile { - return errors.New("unable to lookup values for 'latest' or 'all'") - } else if c.Flags().Lookup("cidfile") == nil { - return errors.New("unable to lookup values for 'latest', 'all' or 'cidfile'") + if !registry.IsRemote() { + specifiedLatest, _ = c.Flags().GetBool("latest") + if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { + if !cidfile { + return errors.New("unable to lookup values for 'latest' or 'all'") + } else if c.Flags().Lookup("cidfile") == nil { + return errors.New("unable to lookup values for 'latest', 'all' or 'cidfile'") + } } } specifiedAll, _ := c.Flags().GetBool("all") - specifiedLatest, _ := c.Flags().GetBool("latest") specifiedCIDFile := false if cid, _ := c.Flags().GetStringArray("cidfile"); len(cid) > 0 { specifiedCIDFile = true @@ -98,17 +102,21 @@ func CheckAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool // CheckAllLatestAndPodIDFile checks that --all and --latest are used correctly. // If withIDFile is set, also check for the --pod-id-file flag. func CheckAllLatestAndPodIDFile(c *cobra.Command, args []string, ignoreArgLen bool, withIDFile bool) error { + var specifiedLatest bool argLen := len(args) - if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { - if !withIDFile { - return errors.New("unable to lookup values for 'latest' or 'all'") - } else if c.Flags().Lookup("pod-id-file") == nil { - return errors.New("unable to lookup values for 'latest', 'all' or 'pod-id-file'") + if !registry.IsRemote() { + // remote clients have no latest flag + specifiedLatest, _ = c.Flags().GetBool("latest") + if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { + if !withIDFile { + return errors.New("unable to lookup values for 'latest' or 'all'") + } else if c.Flags().Lookup("pod-id-file") == nil { + return errors.New("unable to lookup values for 'latest', 'all' or 'pod-id-file'") + } } } specifiedAll, _ := c.Flags().GetBool("all") - specifiedLatest, _ := c.Flags().GetBool("latest") specifiedPodIDFile := false if pid, _ := c.Flags().GetStringArray("pod-id-file"); len(pid) > 0 { specifiedPodIDFile = true diff --git a/cmd/podman/validate/latest.go b/cmd/podman/validate/latest.go index 0ebed7227..a9049ca2c 100644 --- a/cmd/podman/validate/latest.go +++ b/cmd/podman/validate/latest.go @@ -7,9 +7,8 @@ import ( func AddLatestFlag(cmd *cobra.Command, b *bool) { // Initialization flag verification - cmd.Flags().BoolVarP(b, "latest", "l", false, - "Act on the latest container podman is aware of\nNot supported with the \"--remote\" flag") - if registry.IsRemote() { - _ = cmd.Flags().MarkHidden("latest") + if !registry.IsRemote() { + cmd.Flags().BoolVarP(b, "latest", "l", false, + "Act on the latest container podman is aware of\nNot supported with the \"--remote\" flag") } } -- cgit v1.2.3-54-g00ecf From bcd9b8125c4f042e596b2b9263f60825f32e15aa Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Tue, 18 Aug 2020 06:47:19 -0400 Subject: Add support for --connection * override --url and/or --identity fields from containers.conf * --connection flag has higher precedence than ActiveService from containers.conf. Which is set via podman system connection default * Add newline to error message printed on stderr * Added --connection to bash completion and documentation * Updated bindings to query server in case of no path or / Closes #jira-991 Fixes #7276 Signed-off-by: Daniel J Walsh Signed-off-by: Jhon Honce Squashed commits to work around CI issue Signed-off-by: Matthew Heon --- cmd/podman/early_init_linux.go | 2 +- cmd/podman/root.go | 38 +++++++++++++++++++++++++++------ cmd/podman/system/connection/add.go | 2 +- completions/bash/podman | 24 +++++++++++++++------ docs/source/markdown/podman-remote.1.md | 2 +- docs/source/markdown/podman.1.md | 3 +++ 6 files changed, 55 insertions(+), 16 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/early_init_linux.go b/cmd/podman/early_init_linux.go index b43450a7f..8e2f1bd21 100644 --- a/cmd/podman/early_init_linux.go +++ b/cmd/podman/early_init_linux.go @@ -32,7 +32,7 @@ func setUMask() { func earlyInitHook() { if err := setRLimits(); err != nil { - fmt.Fprint(os.Stderr, "Failed to set rlimits: "+err.Error()) + fmt.Fprintf(os.Stderr, "Failed to set rlimits: %s\n", err.Error()) } setUMask() diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 1165f1b84..44e014906 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -111,6 +111,30 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { cfg := registry.PodmanConfig() + // --connection is not as "special" as --remote so we can wait and process it here + var connErr error + conn := cmd.Root().LocalFlags().Lookup("connection") + if conn != nil && conn.Changed { + cfg.Engine.ActiveService = conn.Value.String() + + var err error + cfg.URI, cfg.Identity, err = cfg.ActiveDestination() + if err != nil { + connErr = errors.Wrap(err, "failed to resolve active destination") + } + + if err := cmd.Root().LocalFlags().Set("url", cfg.URI); err != nil { + connErr = errors.Wrap(err, "failed to override --url flag") + } + + if err := cmd.Root().LocalFlags().Set("identity", cfg.Identity); err != nil { + connErr = errors.Wrap(err, "failed to override --identity flag") + } + } + if connErr != nil { + return connErr + } + // Prep the engines if _, err := registry.NewImageEngine(cmd, args); err != nil { return err @@ -211,10 +235,11 @@ func loggingHook() { func rootFlags(cmd *cobra.Command, opts *entities.PodmanConfig) { cfg := opts.Config - uri, ident := resolveDestination() + srv, uri, ident := resolveDestination() lFlags := cmd.Flags() lFlags.BoolVarP(&opts.Remote, "remote", "r", false, "Access remote Podman service (default false)") + lFlags.StringVarP(&opts.Engine.ActiveService, "connection", "c", srv, "Connection to use for remote Podman service") 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)") @@ -264,23 +289,24 @@ func rootFlags(cmd *cobra.Command, opts *entities.PodmanConfig) { } } -func resolveDestination() (string, string) { +func resolveDestination() (string, 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 + return "", uri, ident } cfg, err := config.ReadCustomConfig() if err != nil { - return registry.DefaultAPIAddress(), "" + logrus.Warning(errors.Wrap(err, "unable to read local containers.conf")) + return "", registry.DefaultAPIAddress(), "" } uri, ident, err := cfg.ActiveDestination() if err != nil { - return registry.DefaultAPIAddress(), "" + return "", registry.DefaultAPIAddress(), "" } - return uri, ident + return cfg.Engine.ActiveService, uri, ident } diff --git a/cmd/podman/system/connection/add.go b/cmd/podman/system/connection/add.go index 7522eb190..77a2edf8a 100644 --- a/cmd/podman/system/connection/add.go +++ b/cmd/podman/system/connection/add.go @@ -95,7 +95,7 @@ func add(cmd *cobra.Command, args []string) error { uri.Host = net.JoinHostPort(uri.Hostname(), cmd.Flag("port").DefValue) } - if uri.Path == "" { + if uri.Path == "" || uri.Path == "/" { if uri.Path, err = getUDS(cmd, uri); err != nil { return errors.Wrapf(err, "failed to connect to %q", uri.String()) } diff --git a/completions/bash/podman b/completions/bash/podman index abcf54416..bd72649bb 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -3546,22 +3546,32 @@ _podman_volume() { _podman_podman() { local options_with_args=" - --config -c + --cni-config-dir + --conmon + --connection -c + --events-backend + --hooks-dir + --identity + --log-level + --namespace + --network-cmd-path --root --runroot --storage-driver --storage-opt - --log-level - --namespace - " + --tmpdir + --runtime + --url + " local boolean_options=" --help - -h + --syslog --version + -h + -r, --remote -v - --syslog " - commands=" + commands=" attach auto-update build diff --git a/docs/source/markdown/podman-remote.1.md b/docs/source/markdown/podman-remote.1.md index 0d7be1e5d..23ccaf0e6 100644 --- a/docs/source/markdown/podman-remote.1.md +++ b/docs/source/markdown/podman-remote.1.md @@ -23,7 +23,7 @@ Podman-remote provides a local client interacting with a Podman backend node thr ## GLOBAL OPTIONS -**--connection**=*name* +**--connection**=*name*, **-c** Remote connection name diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md index 01a054ff4..7839ce8fb 100644 --- a/docs/source/markdown/podman.1.md +++ b/docs/source/markdown/podman.1.md @@ -31,6 +31,9 @@ Note: CGroup manager is not supported in rootless mode when using CGroups Versio **--cni-config-dir** Path of the configuration directory for CNI networks. (Default: `/etc/cni/net.d`) +**--connection**, **-c** +Connection to use for remote podman (Default connection is configured in `containers.conf`) + **--conmon** Path of the conmon binary (Default path is configured in `containers.conf`) -- cgit v1.2.3-54-g00ecf From de75ae29ff461ebc4932a2ad7047855f51797dda Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 20 Aug 2020 12:52:18 -0400 Subject: Fix imports (podman -> libpod for v2.0 branch) Signed-off-by: Matthew Heon --- cmd/podman/validate/args.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cmd') diff --git a/cmd/podman/validate/args.go b/cmd/podman/validate/args.go index aacb41e69..cf500c0a3 100644 --- a/cmd/podman/validate/args.go +++ b/cmd/podman/validate/args.go @@ -4,7 +4,7 @@ import ( "fmt" "strconv" - "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/libpod/v2/cmd/podman/registry" "github.com/pkg/errors" "github.com/spf13/cobra" ) -- cgit v1.2.3-54-g00ecf From 7fc3c25410bd5ee053473ffd5df2209f41840ec0 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Thu, 20 Aug 2020 11:07:36 +0200 Subject: fix pod creation with "new:" syntax followup + allow hostname Fixes: 4c75fe3f70ed ("fix pod creation with "new:" syntax") Commit 4c75fe3f70ed passes all net options to the pod but forgot to unset the options for the container creation. This leads to erros when using flags like `--ip` since we tried setting the ip on the pod and container which obviously fails. I didn't notice the bug because we don't throw an error when specifing port bindings on a container which joins the pods network namespace. (#7373) Also allow the use of `--hostname` and pass that option to the pod and unset it for the container. The container has to use the pods hostname anyway. This would error otherwise. Added tests to prevent regression. Signed-off-by: Paul Holzinger --- cmd/podman/containers/create.go | 5 +++++ test/e2e/run_networking_test.go | 32 ++++++++++++++++++++++++++++++-- test/e2e/run_test.go | 8 ++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) (limited to 'cmd') diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index 0cd56f540..04a6ff9ba 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -292,7 +292,12 @@ func createPodIfNecessary(s *specgen.SpecGenerator, netOpts *entities.NetOptions Infra: true, Net: netOpts, CreateCommand: os.Args, + Hostname: s.ContainerBasicConfig.Hostname, } + // Unset config values we passed to the pod to prevent them being used twice for the container and pod. + s.ContainerBasicConfig.Hostname = "" + s.ContainerNetworkConfig = specgen.ContainerNetworkConfig{} + s.Pod = podName return registry.ContainerEngine().PodCreate(context.Background(), createOptions) } diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 88e289f1f..a33b23fab 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -437,8 +437,8 @@ var _ = Describe("Podman run networking", func() { It("podman run in custom CNI network with --static-ip", func() { SkipIfRootless() netName := "podmantestnetwork" - ipAddr := "10.20.30.128" - create := podmanTest.Podman([]string{"network", "create", "--subnet", "10.20.30.0/24", netName}) + ipAddr := "10.25.30.128" + create := podmanTest.Podman([]string{"network", "create", "--subnet", "10.25.30.0/24", netName}) create.WaitWithDefaultTimeout() Expect(create.ExitCode()).To(BeZero()) @@ -446,5 +446,33 @@ var _ = Describe("Podman run networking", func() { run.WaitWithDefaultTimeout() Expect(run.ExitCode()).To(BeZero()) Expect(run.OutputToString()).To(ContainSubstring(ipAddr)) + + netrm := podmanTest.Podman([]string{"network", "rm", netName}) + netrm.WaitWithDefaultTimeout() + Expect(netrm.ExitCode()).To(BeZero()) + }) + + It("podman run with new:pod and static-ip", func() { + SkipIfRemote() + SkipIfRootless() + netName := "podmantestnetwork2" + ipAddr := "10.25.40.128" + podname := "testpod" + create := podmanTest.Podman([]string{"network", "create", "--subnet", "10.25.40.0/24", netName}) + create.WaitWithDefaultTimeout() + Expect(create.ExitCode()).To(BeZero()) + + run := podmanTest.Podman([]string{"run", "-t", "-i", "--rm", "--pod", "new:" + podname, "--net", netName, "--ip", ipAddr, ALPINE, "ip", "addr"}) + run.WaitWithDefaultTimeout() + Expect(run.ExitCode()).To(BeZero()) + Expect(run.OutputToString()).To(ContainSubstring(ipAddr)) + + podrm := podmanTest.Podman([]string{"pod", "rm", "-f", podname}) + podrm.WaitWithDefaultTimeout() + Expect(podrm.ExitCode()).To(BeZero()) + + netrm := podmanTest.Podman([]string{"network", "rm", netName}) + netrm.WaitWithDefaultTimeout() + Expect(netrm.ExitCode()).To(BeZero()) }) }) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 681498ea1..6064be122 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -835,6 +835,14 @@ USER mail` Expect(match).To(BeTrue()) }) + It("podman run --pod new with hostname", func() { + hostname := "abc" + session := podmanTest.Podman([]string{"run", "--pod", "new:foobar", "--hostname", hostname, ALPINE, "cat", "/etc/hostname"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring(hostname)) + }) + It("podman run --rm should work", func() { session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "ls"}) session.WaitWithDefaultTimeout() -- cgit v1.2.3-54-g00ecf