mirror of
https://github.com/containers/podman.git
synced 2025-06-26 21:07:02 +08:00
Merge pull request #11819 from trynaeat/dial-stdio
Adding dial-stdio CLI cmd
This commit is contained in:
145
cmd/podman/system/dial_stdio.go
Normal file
145
cmd/podman/system/dial_stdio.go
Normal file
@ -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()
|
||||
}
|
@ -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) {
|
||||
|
53
test/e2e/system_dial_stdio_test.go
Normal file
53
test/e2e/system_dial_stdio_test.go
Normal file
@ -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"))
|
||||
})
|
||||
})
|
Reference in New Issue
Block a user