Projects STRLCPY cdebug Commits 076f8eec
🤬
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
  • ■ ■ ■ ■ ■ ■
    README.md
     1 +# cdebug - experimental container debugger
     2 + 
     3 +Work in progres...
     4 + 
  • ■ ■ ■ ■ ■ ■
    cmd/cli.go
     1 +package cmd
     2 + 
     3 +import (
     4 + "io"
     5 + 
     6 + "github.com/docker/cli/cli/streams"
     7 +)
     8 + 
     9 +type Streams interface {
     10 + InputStream() *streams.In
     11 + OutputStream() *streams.Out
     12 + ErrorStream() io.Writer
     13 +}
     14 + 
     15 +type CLI interface {
     16 + Streams
     17 +}
     18 + 
     19 +type cli struct {
     20 + inputStream *streams.In
     21 + outputStream *streams.Out
     22 + errorStream io.Writer
     23 +}
     24 + 
     25 +var _ CLI = &cli{}
     26 + 
     27 +func NewCLI(cin io.ReadCloser, cout io.Writer, cerr io.Writer) CLI {
     28 + return &cli{
     29 + inputStream: streams.NewIn(cin),
     30 + outputStream: streams.NewOut(cout),
     31 + errorStream: cerr,
     32 + }
     33 +}
     34 + 
     35 +func (c *cli) InputStream() *streams.In {
     36 + return c.inputStream
     37 +}
     38 + 
     39 +func (c *cli) OutputStream() *streams.Out {
     40 + return c.outputStream
     41 +}
     42 + 
     43 +func (c *cli) ErrorStream() io.Writer {
     44 + return c.errorStream
     45 +}
     46 + 
  • ■ ■ ■ ■ ■ ■
    cmd/exec/exec.go
     1 +package exec
     2 + 
     3 +import (
     4 + "context"
     5 + "fmt"
     6 + "io"
     7 + "strings"
     8 + 
     9 + "github.com/docker/docker/api/types"
     10 + "github.com/docker/docker/api/types/container"
     11 + dockerclient "github.com/docker/docker/client"
     12 + "github.com/google/uuid"
     13 + "github.com/sirupsen/logrus"
     14 + "github.com/spf13/cobra"
     15 + 
     16 + "github.com/iximiuz/cdebug/cmd"
     17 +)
     18 + 
     19 +// cdebug exec [--image busybox] <CONT> [CMD]
     20 +// cdebug exec [-it] --image nixery.dev/shell/ps <CONT> [CMD]
     21 + 
     22 +// cdebug images
     23 +// - busybox
     24 +// - alpine
     25 +// - nixery:shell/ps
     26 + 
     27 +const (
     28 + defaultToolkitImage = "docker.io/library/busybox:latest"
     29 +)
     30 + 
     31 +type options struct {
     32 + target string
     33 + name string
     34 + image string
     35 + tty bool
     36 + stdin bool
     37 + cmd []string
     38 +}
     39 + 
     40 +func NewCommand(cli cmd.CLI) *cobra.Command {
     41 + var opts options
     42 + 
     43 + cmd := &cobra.Command{
     44 + Use: "exec [OPTIONS] CONTAINER [COMMAND] [ARG...]",
     45 + Short: "Start a debugger shell in the target container",
     46 + Args: cobra.MinimumNArgs(1),
     47 + RunE: func(cmd *cobra.Command, args []string) error {
     48 + opts.target = args[0]
     49 + if len(args) > 1 {
     50 + opts.cmd = args[1:]
     51 + }
     52 + return runDebugger(context.Background(), cli, &opts)
     53 + },
     54 + }
     55 + 
     56 + flags := cmd.Flags()
     57 + flags.SetInterspersed(false) // Instead of relying on --
     58 + 
     59 + flags.StringVar(
     60 + &opts.name,
     61 + "name",
     62 + "",
     63 + "Assign a name to the debugger container",
     64 + )
     65 + flags.StringVar(
     66 + &opts.image,
     67 + "image",
     68 + defaultToolkitImage,
     69 + "Debugging toolkit image (hint: use 'busybox' or 'nixery.dev/shell/tool1/tool2/etc...')",
     70 + )
     71 + flags.BoolVarP(
     72 + &opts.stdin,
     73 + "interactive",
     74 + "i",
     75 + false,
     76 + "Keep the STDIN open (same as in `docker exec -i`)",
     77 + )
     78 + flags.BoolVarP(
     79 + &opts.tty,
     80 + "tty",
     81 + "t",
     82 + false,
     83 + "Allocate a pseudo-TTY (same as in `docker exec -t`)",
     84 + )
     85 + 
     86 + return cmd
     87 +}
     88 + 
     89 +const chrootProgramBusybox = `
     90 +set -euxo pipefail
     91 + 
     92 +rm -rf /proc/1/root/.cdebug
     93 +ln -s /proc/$$/root/bin/ /proc/1/root/.cdebug
     94 +export PATH=$PATH:/.cdebug
     95 +chroot /proc/1/root sh
     96 +`
     97 + 
     98 +func runDebugger(ctx context.Context, cli cmd.CLI, opts *options) error {
     99 + if err := cli.InputStream().CheckTty(opts.stdin, opts.tty); err != nil {
     100 + return err
     101 + }
     102 + 
     103 + client, err := dockerclient.NewClientWithOpts(
     104 + dockerclient.FromEnv,
     105 + dockerclient.WithAPIVersionNegotiation(),
     106 + )
     107 + if err != nil {
     108 + return fmt.Errorf("cannot initialize Docker client: %w", err)
     109 + }
     110 + 
     111 + if err := pullImage(ctx, cli, client, opts.image); err != nil {
     112 + return err
     113 + }
     114 + 
     115 + target := "container:" + opts.target
     116 + resp, err := client.ContainerCreate(
     117 + ctx,
     118 + &container.Config{
     119 + Image: opts.image,
     120 + Cmd: []string{"sh", "-c", chrootProgramBusybox},
     121 + // AttachStdin: true,
     122 + OpenStdin: opts.stdin,
     123 + Tty: opts.tty,
     124 + },
     125 + &container.HostConfig{
     126 + NetworkMode: container.NetworkMode(target),
     127 + PidMode: container.PidMode(target),
     128 + UTSMode: container.UTSMode(target),
     129 + // TODO: IpcMode: container.IpcMode("container:my-distroless"),
     130 + },
     131 + nil,
     132 + nil,
     133 + debuggerName(opts.name),
     134 + )
     135 + if err != nil {
     136 + return fmt.Errorf("cannot create debugger container: %w", err)
     137 + }
     138 + 
     139 + if opts.stdin || opts.tty {
     140 + close, err := attachDebugger(ctx, cli, client, opts, resp.ID)
     141 + if err != nil {
     142 + return fmt.Errorf("cannot attach to debugger container: %w", err)
     143 + }
     144 + defer close()
     145 + }
     146 + 
     147 + if err := client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
     148 + return fmt.Errorf("cannot start debugger container: %w", err)
     149 + }
     150 + 
     151 + statusCh, errCh := client.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
     152 + select {
     153 + case err := <-errCh:
     154 + if err != nil {
     155 + return fmt.Errorf("waiting debugger container failed: %w", err)
     156 + }
     157 + case <-statusCh:
     158 + }
     159 + 
     160 + return nil
     161 +}
     162 + 
     163 +func pullImage(
     164 + ctx context.Context,
     165 + cli cmd.CLI,
     166 + client *dockerclient.Client,
     167 + image string,
     168 +) error {
     169 + resp, err := client.ImagePull(ctx, image, types.ImagePullOptions{})
     170 + if err != nil {
     171 + return fmt.Errorf("cannot pull debugger image %q: %w", image, err)
     172 + }
     173 + defer resp.Close()
     174 + 
     175 + _, err = io.Copy(cli.OutputStream(), resp)
     176 + return err
     177 +}
     178 + 
     179 +func attachDebugger(
     180 + ctx context.Context,
     181 + cli cmd.CLI,
     182 + client *dockerclient.Client,
     183 + opts *options,
     184 + contID string,
     185 +) (func(), error) {
     186 + resp, err := client.ContainerAttach(ctx, contID, types.ContainerAttachOptions{
     187 + Stream: true,
     188 + Stdin: opts.stdin,
     189 + Stdout: true,
     190 + Stderr: true,
     191 + })
     192 + if err != nil {
     193 + return nil, fmt.Errorf("cannot attach to debugger container: %w", err)
     194 + }
     195 + 
     196 + var cin io.ReadCloser
     197 + if opts.stdin {
     198 + cin = cli.InputStream()
     199 + }
     200 + 
     201 + var cout io.Writer = cli.OutputStream()
     202 + var cerr io.Writer = cli.ErrorStream()
     203 + if opts.tty {
     204 + cerr = cli.OutputStream()
     205 + }
     206 + 
     207 + go func() {
     208 + s := ioStreamer{
     209 + streams: cli,
     210 + inputStream: cin,
     211 + outputStream: cout,
     212 + errorStream: cerr,
     213 + resp: resp,
     214 + tty: opts.tty,
     215 + stdin: opts.stdin,
     216 + }
     217 + 
     218 + if err := s.stream(ctx); err != nil {
     219 + logrus.WithError(err).Warn("ioStreamer.stream() failed")
     220 + }
     221 + }()
     222 + 
     223 + return resp.Close, nil
     224 +}
     225 + 
     226 +type ioStreamer struct {
     227 + streams cmd.Streams
     228 + 
     229 + inputStream io.ReadCloser
     230 + outputStream io.Writer
     231 + errorStream io.Writer
     232 + 
     233 + resp types.HijackedResponse
     234 + 
     235 + stdin bool
     236 + tty bool
     237 +}
     238 + 
     239 +func (s *ioStreamer) stream(ctx context.Context) error {
     240 + // TODO: check stdin && tty flags
     241 + s.streams.InputStream().SetRawTerminal()
     242 + s.streams.OutputStream().SetRawTerminal()
     243 + defer func() {
     244 + s.streams.InputStream().RestoreTerminal()
     245 + s.streams.OutputStream().RestoreTerminal()
     246 + }()
     247 + 
     248 + inDone := make(chan error)
     249 + go func() {
     250 + io.Copy(s.resp.Conn, s.inputStream)
     251 + close(inDone)
     252 + }()
     253 + 
     254 + outDone := make(chan error)
     255 + go func() {
     256 + io.Copy(s.outputStream, s.resp.Reader)
     257 + close(outDone)
     258 + }()
     259 + 
     260 + select {
     261 + case <-ctx.Done():
     262 + return ctx.Err()
     263 + case <-inDone:
     264 + <-outDone
     265 + return nil
     266 + case <-outDone:
     267 + return nil
     268 + }
     269 + 
     270 + return nil
     271 +}
     272 + 
     273 +func debuggerName(name string) string {
     274 + if len(name) > 0 {
     275 + return name
     276 + }
     277 + 
     278 + return "cdebug-" + strings.Split(uuid.NewString(), "-")[0]
     279 +}
     280 + 
  • ■ ■ ■ ■ ■ ■
    go.mod
    skipped 4 lines
    5 5  require (
    6 6   github.com/docker/cli v20.10.20+incompatible
    7 7   github.com/docker/docker v20.10.20+incompatible
     8 + github.com/google/uuid v1.3.0
    8 9   github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae
    9 10   github.com/sirupsen/logrus v1.9.0
     11 + github.com/spf13/cobra v1.6.0
    10 12  )
    11 13   
    12 14  require (
    skipped 4 lines
    17 19   github.com/docker/go-units v0.5.0 // indirect
    18 20   github.com/gogo/protobuf v1.3.2 // indirect
    19 21   github.com/google/go-cmp v0.5.5 // indirect
     22 + github.com/inconshreveable/mousetrap v1.0.1 // indirect
    20 23   github.com/morikuni/aec v1.0.0 // indirect
    21 24   github.com/opencontainers/go-digest v1.0.0 // indirect
    22 25   github.com/opencontainers/image-spec v1.0.2 // indirect
    23 26   github.com/pkg/errors v0.9.1 // indirect
     27 + github.com/spf13/pflag v1.0.5 // indirect
    24 28   golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
    25 29   golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
    26 30   golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
    skipped 5 lines
  • ■ ■ ■ ■ ■ ■
    go.sum
    skipped 1 lines
    2 2  github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
    3 3  github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
    4 4  github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
     5 +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
    5 6  github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
    6 7  github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
    7 8  github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
    skipped 15 lines
    23 24  github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
    24 25  github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
    25 26  github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
     27 +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
     28 +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
     29 +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
     30 +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
    26 31  github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
    27 32  github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
    28 33  github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI=
    skipped 9 lines
    38 43  github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
    39 44  github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
    40 45  github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
     46 +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
    41 47  github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
    42 48  github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
     49 +github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI=
     50 +github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
    43 51  github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
     52 +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
     53 +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
    44 54  github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
    45 55  github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
    46 56  github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
    skipped 41 lines
    88 98  golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
    89 99  golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
    90 100  gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
    91  -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
    92 101  gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
     102 +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
     103 +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
    93 104  gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
    94 105  gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
    95 106  gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
    skipped 1 lines
  • ■ ■ ■ ■ ■
    main.go
    1 1  package main
    2 2   
    3 3  import (
    4  - "context"
    5  - "io"
     4 + "fmt"
    6 5   "os"
    7 6   
    8  - clistreams "github.com/docker/cli/cli/streams"
    9  - "github.com/docker/docker/api/types"
    10  - "github.com/docker/docker/api/types/container"
    11  - "github.com/docker/docker/client"
    12 7   "github.com/moby/term"
    13  - "github.com/sirupsen/logrus"
    14  -)
    15  - 
    16  -// cdebug exec [--image busybox] <CONT> [CMD]
    17  -// cdebug exec [-it] --image nixery.dev/shell/ps <CONT> [CMD]
     8 + "github.com/spf13/cobra"
    18 9   
    19  -// cdebug images
    20  -// - busybox
    21  -// - alpine
    22  -// - nixery:shell/ps
    23  - 
    24  -const chrootProgram = `
    25  -set -euxo pipefail
    26  - 
    27  -rm -rf /proc/1/root/.cdebug
    28  -ln -s /proc/$$/root/bin/ /proc/1/root/.cdebug
    29  -export PATH=$PATH:/.cdebug
    30  -chroot /proc/1/root sh
    31  -`
    32  - 
    33  -type Streams struct {
    34  - In *clistreams.In
    35  - Out *clistreams.Out
    36  - Err io.Writer
    37  -}
     10 + "github.com/iximiuz/cdebug/cmd"
     11 + "github.com/iximiuz/cdebug/cmd/exec"
     12 +)
    38 13   
    39 14  func main() {
    40 15   stdin, stdout, stderr := term.StdStreams()
    41  - streams := &Streams{
    42  - In: clistreams.NewIn(stdin),
    43  - Out: clistreams.NewOut(stdout),
    44  - Err: stderr,
    45  - }
    46  - 
    47  - ctx := context.Background()
    48  - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
    49  - if err != nil {
    50  - panic(err)
    51  - }
    52  - 
    53  - reader, err := cli.ImagePull(ctx, "docker.io/library/busybox", types.ImagePullOptions{})
    54  - if err != nil {
    55  - panic(err)
    56  - }
    57  - 
    58  - io.Copy(os.Stdout, reader)
    59  - reader.Close()
    60  - 
    61  - resp, err := cli.ContainerCreate(ctx, &container.Config{
    62  - Image: "busybox",
    63  - Cmd: []string{"sh", "-c", chrootProgram},
    64  - // AttachStdin: true,
    65  - OpenStdin: true,
    66  - Tty: true,
    67  - }, &container.HostConfig{
    68  - NetworkMode: container.NetworkMode("container:my-distroless"),
    69  - PidMode: container.PidMode("container:my-distroless"),
    70  - // IpcMode: container.IpcMode("container:my-distroless"),
    71  - UTSMode: container.UTSMode("container:my-distroless"),
    72  - }, nil, nil, "")
    73  - if err != nil {
    74  - panic(err)
    75  - }
    76  - 
    77  - close, err := attachContainer(ctx, cli, streams, resp.ID)
    78  - if err != nil {
    79  - panic(err)
    80  - }
    81  - defer close()
    82  - 
    83  - if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
    84  - panic(err)
    85  - }
    86  - 
    87  - statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
    88  - select {
    89  - case err := <-errCh:
    90  - if err != nil {
    91  - panic(err)
    92  - }
    93  - case <-statusCh:
    94  - }
    95  -}
    96  - 
    97  -func attachContainer(ctx context.Context, cli *client.Client, streams *Streams, contID string) (func(), error) {
    98  - options := types.ContainerAttachOptions{
    99  - Stream: true,
    100  - Stdin: true,
    101  - Stdout: true,
    102  - Stderr: true,
    103  - }
     16 + cli := cmd.NewCLI(stdin, stdout, stderr)
    104 17   
    105  - resp, err := cli.ContainerAttach(ctx, contID, options)
    106  - if err != nil {
    107  - return nil, err
     18 + cmd := &cobra.Command{
     19 + Use: "cdebug [OPTIONS] COMMAND [ARG...]",
     20 + Short: "The base command for the cdebug CLI.",
    108 21   }
    109 22   
    110  - var (
    111  - out io.Writer = os.Stdout
    112  - cerr io.Writer = os.Stdout
    113  - in io.ReadCloser = os.Stdin
     23 + cmd.AddCommand(
     24 + exec.NewCommand(cli),
     25 + // TODO: other commands
    114 26   )
    115 27   
    116  - go func() {
    117  - s := ioStreamer{
    118  - streams: streams,
    119  - inputStream: in,
    120  - outputStream: out,
    121  - errorStream: cerr,
    122  - resp: resp,
    123  - }
    124  - 
    125  - if err := s.stream(ctx); err != nil {
    126  - logrus.WithError(err).Warn("ioStreamer.stream() failed")
    127  - }
    128  - }()
    129  - 
    130  - return resp.Close, nil
    131  -}
    132  - 
    133  -type ioStreamer struct {
    134  - streams *Streams
    135  - 
    136  - inputStream io.ReadCloser
    137  - outputStream io.Writer
    138  - errorStream io.Writer
    139  - 
    140  - resp types.HijackedResponse
    141  -}
    142  - 
    143  -func (s *ioStreamer) stream(ctx context.Context) error {
    144  - s.streams.In.SetRawTerminal()
    145  - s.streams.Out.SetRawTerminal()
    146  - defer func() {
    147  - s.streams.In.RestoreTerminal()
    148  - s.streams.Out.RestoreTerminal()
    149  - }()
    150  - 
    151  - inDone := make(chan error)
    152  - go func() {
    153  - io.Copy(s.resp.Conn, s.inputStream)
    154  - close(inDone)
    155  - }()
    156  - 
    157  - outDone := make(chan error)
    158  - go func() {
    159  - io.Copy(s.outputStream, s.resp.Reader)
    160  - close(outDone)
    161  - }()
    162  - 
    163  - select {
    164  - case <-ctx.Done():
    165  - return ctx.Err()
    166  - case <-inDone:
    167  - <-outDone
    168  - return nil
    169  - case <-outDone:
    170  - return nil
     28 + if err := cmd.Execute(); err != nil {
     29 + fmt.Fprintln(stderr, err.Error())
     30 + os.Exit(1)
    171 31   }
    172  - 
    173  - return nil
    174 32  }
    175 33   
Please wait...
Page is in error, reload to recover