aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/system/dial_stdio.go145
-rw-r--r--pkg/bindings/connection.go11
-rw-r--r--test/e2e/system_dial_stdio_test.go53
3 files changed, 209 insertions, 0 deletions
diff --git a/cmd/podman/system/dial_stdio.go b/cmd/podman/system/dial_stdio.go
new file mode 100644
index 000000000..eae89f38e
--- /dev/null
+++ b/cmd/podman/system/dial_stdio.go
@@ -0,0 +1,145 @@
+package system
+
+import (
+ "context"
+ "io"
+ "os"
+
+ "github.com/containers/podman/v3/cmd/podman/registry"
+ "github.com/containers/podman/v3/cmd/podman/validate"
+ "github.com/containers/podman/v3/pkg/bindings"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+)
+
+var (
+ dialStdioCommand = &cobra.Command{
+ Use: "dial-stdio",
+ Short: "Proxy the stdio stream to the daemon connection. Should not be invoked manually.",
+ Args: validate.NoArgs,
+ Hidden: true,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ return runDialStdio()
+ },
+ Example: "podman system dial-stdio",
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Command: dialStdioCommand,
+ Parent: systemCmd,
+ })
+}
+
+func runDialStdio() error {
+ ctx := registry.Context()
+ cfg := registry.PodmanConfig()
+ ctx, cancel := context.WithCancel(ctx)
+ defer cancel()
+ bindCtx, err := bindings.NewConnection(ctx, cfg.URI)
+ if err != nil {
+ return errors.Wrap(err, "failed to open connection to podman")
+ }
+ conn, err := bindings.GetClient(bindCtx)
+ if err != nil {
+ return errors.Wrap(err, "failed to get connection after initialization")
+ }
+ netConn, err := conn.GetDialer(bindCtx)
+ if err != nil {
+ return errors.Wrap(err, "failed to open the raw stream connection")
+ }
+ defer netConn.Close()
+
+ var connHalfCloser halfCloser
+ switch t := netConn.(type) {
+ case halfCloser:
+ connHalfCloser = t
+ case halfReadWriteCloser:
+ connHalfCloser = &nopCloseReader{t}
+ default:
+ return errors.New("the raw stream connection does not implement halfCloser")
+ }
+
+ stdin2conn := make(chan error, 1)
+ conn2stdout := make(chan error, 1)
+ go func() {
+ stdin2conn <- copier(connHalfCloser, &halfReadCloserWrapper{os.Stdin}, "stdin to stream")
+ }()
+ go func() {
+ conn2stdout <- copier(&halfWriteCloserWrapper{os.Stdout}, connHalfCloser, "stream to stdout")
+ }()
+ select {
+ case err = <-stdin2conn:
+ if err != nil {
+ return err
+ }
+ // wait for stdout
+ err = <-conn2stdout
+ case err = <-conn2stdout:
+ // return immediately
+ }
+ return err
+}
+
+// Below portion taken from original docker CLI
+// https://github.com/docker/cli/blob/v20.10.9/cli/command/system/dial_stdio.go
+func copier(to halfWriteCloser, from halfReadCloser, debugDescription string) error {
+ defer func() {
+ if err := from.CloseRead(); err != nil {
+ logrus.Errorf("error while CloseRead (%s): %v", debugDescription, err)
+ }
+ if err := to.CloseWrite(); err != nil {
+ logrus.Errorf("error while CloseWrite (%s): %v", debugDescription, err)
+ }
+ }()
+ if _, err := io.Copy(to, from); err != nil {
+ return errors.Wrapf(err, "error while Copy (%s)", debugDescription)
+ }
+ return nil
+}
+
+type halfReadCloser interface {
+ io.Reader
+ CloseRead() error
+}
+
+type halfWriteCloser interface {
+ io.Writer
+ CloseWrite() error
+}
+
+type halfCloser interface {
+ halfReadCloser
+ halfWriteCloser
+}
+
+type halfReadWriteCloser interface {
+ io.Reader
+ halfWriteCloser
+}
+
+type nopCloseReader struct {
+ halfReadWriteCloser
+}
+
+func (x *nopCloseReader) CloseRead() error {
+ return nil
+}
+
+type halfReadCloserWrapper struct {
+ io.ReadCloser
+}
+
+func (x *halfReadCloserWrapper) CloseRead() error {
+ return x.Close()
+}
+
+type halfWriteCloserWrapper struct {
+ io.WriteCloser
+}
+
+func (x *halfWriteCloserWrapper) CloseWrite() error {
+ return x.Close()
+}
diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go
index e2c46e481..dc75dac5a 100644
--- a/pkg/bindings/connection.go
+++ b/pkg/bindings/connection.go
@@ -349,6 +349,17 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string,
return &APIResponse{response, req}, err
}
+// Get raw Transport.DialContext from client
+func (c *Connection) GetDialer(ctx context.Context) (net.Conn, error) {
+ client := c.Client
+ transport := client.Transport.(*http.Transport)
+ if transport.DialContext != nil && transport.TLSClientConfig == nil {
+ return transport.DialContext(ctx, c.URI.Scheme, c.URI.String())
+ }
+
+ return nil, errors.New("Unable to get dial context")
+}
+
// FiltersToString converts our typical filter format of a
// map[string][]string to a query/html safe string.
func FiltersToString(filters map[string][]string) (string, error) {
diff --git a/test/e2e/system_dial_stdio_test.go b/test/e2e/system_dial_stdio_test.go
new file mode 100644
index 000000000..afe3d5acd
--- /dev/null
+++ b/test/e2e/system_dial_stdio_test.go
@@ -0,0 +1,53 @@
+package integration
+
+import (
+ "fmt"
+ "os"
+
+ . "github.com/containers/podman/v3/test/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("podman system dial-stdio", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest *PodmanTestIntegration
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanTestCreate(tempdir)
+ podmanTest.Setup()
+ podmanTest.SeedImages()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds())
+ GinkgoWriter.Write([]byte(timedResult))
+ })
+
+ It("podman system dial-stdio help", func() {
+ session := podmanTest.Podman([]string{"system", "dial-stdio", "--help"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(0))
+ Expect(session.OutputToString()).To(ContainSubstring("Examples: podman system dial-stdio"))
+ })
+
+ It("podman system dial-stdio while service is not running", func() {
+ if IsRemote() {
+ Skip("this test is only for non-remote")
+ }
+ session := podmanTest.Podman([]string{"system", "dial-stdio"})
+ session.WaitWithDefaultTimeout()
+ Expect(session).Should(Exit(125))
+ Expect(session.ErrorToString()).To(ContainSubstring("Error: failed to open connection to podman"))
+ })
+})