Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Adding dial-stdio CLI cmd

Signed-off-by: Jake Parks <jamesparks10@gmail.com>

Made dial-stdio URI configurable

Slight refactors

Signed-off-by: Jake Parks <jamesparks10@gmail.com>

Added simple test for existence of `podman system dial-stdio` command

Fix 'system dial-stdio' integration tests

Changed link in comment to permalink
  • Loading branch information
trynaeat committed Oct 6, 2021
1 parent bfb904b commit 6f9e9ee
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 0 deletions.
145 changes: 145 additions & 0 deletions cmd/podman/system/dial_stdio.go
Original file line number Diff line number Diff line change
@@ -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()
}
11 changes: 11 additions & 0 deletions pkg/bindings/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
53 changes: 53 additions & 0 deletions test/e2e/system_dial_stdio_test.go
Original file line number Diff line number Diff line change
@@ -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"))
})
})

0 comments on commit 6f9e9ee

Please sign in to comment.