go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/connection/container/docker_engine/command.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package docker_engine
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"context"
    10  	"encoding/binary"
    11  	"io"
    12  	"time"
    13  
    14  	docker "github.com/docker/docker/api/types"
    15  	"github.com/docker/docker/client"
    16  	"go.mondoo.com/cnquery/providers/os/connection/shared"
    17  )
    18  
    19  type Command struct {
    20  	shared.Command
    21  	Container string
    22  	Client    *client.Client
    23  }
    24  
    25  func (c *Command) Exec(command string) (*shared.Command, error) {
    26  	c.Command.Command = command
    27  	c.Command.Stats.Start = time.Now()
    28  
    29  	ctx := context.Background()
    30  	res, err := c.Client.ContainerExecCreate(ctx, c.Container, docker.ExecConfig{
    31  		Cmd:          []string{"/bin/sh", "-c", c.Command.Command},
    32  		Detach:       true,
    33  		Tty:          false,
    34  		AttachStdin:  false,
    35  		AttachStderr: true,
    36  		AttachStdout: true,
    37  	})
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  
    42  	resp, err := c.Client.ContainerExecAttach(ctx, res.ID, docker.ExecStartCheck{
    43  		Detach: false,
    44  		Tty:    false,
    45  	})
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	// TODO: transformHijack breaks for long stdout, but not if we read stdout/stderr in upfront
    51  	content, err := io.ReadAll(resp.Reader)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	var stdoutBuffer bytes.Buffer
    57  	var stderrBuffer bytes.Buffer
    58  
    59  	// create buffered stream
    60  	c.Command.Stdout = &stdoutBuffer
    61  	c.Command.Stderr = &stderrBuffer
    62  
    63  	stdOutWriter := bufio.NewWriter(&stdoutBuffer)
    64  	stdErrWriter := bufio.NewWriter(&stderrBuffer)
    65  
    66  	// extract stdout, stderr
    67  	c.transformHijack(bytes.NewReader(content), stdOutWriter, stdErrWriter)
    68  
    69  	defer stdOutWriter.Flush()
    70  	defer stdErrWriter.Flush()
    71  
    72  	c.Command.Stats.Duration = time.Since(c.Command.Stats.Start)
    73  
    74  	info, err := c.Client.ContainerExecInspect(ctx, res.ID)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	c.Command.ExitStatus = info.ExitCode
    79  
    80  	return &c.Command, nil
    81  }
    82  
    83  const (
    84  	STDIN  byte = 0
    85  	STDOUT byte = 1
    86  	STDERR byte = 2
    87  )
    88  
    89  // Format is defined in https://docs.docker.com/engine/api/v1.33/#operation/ContainerAttach
    90  func (c *Command) transformHijack(docker io.Reader, stdout io.Writer, stderr io.Writer) {
    91  	header := make([]byte, 8)
    92  	for {
    93  		// read header
    94  		_, err := docker.Read(header)
    95  
    96  		// end reached
    97  		if err == io.EOF {
    98  			break
    99  		}
   100  
   101  		size := binary.BigEndian.Uint32(header[4:8])
   102  		content := make([]byte, size)
   103  		_, err = docker.Read(content)
   104  
   105  		if header[0] == STDIN || header[0] == STDOUT {
   106  			stdout.Write(content)
   107  		} else if header[0] == STDERR {
   108  			stderr.Write(content)
   109  		}
   110  
   111  		// end reached
   112  		if err == io.EOF {
   113  			break
   114  		}
   115  	}
   116  }