<p>I'm using the golang kubernetes client to create kubernetes pods and execute remote commands in them. However, I'm finding that I can't get feedback on the status of the remote execution until it's finished because I can't figure out how to stream the logs of the remote command. Here's my current implementation for executing a remote command:</p>
<pre class="lang-golang prettyprint-override"><code>func (k *KubernetesClient) RunCommand(ctx context.Context, args *RunCommandArgs) (string, int, error) {
    req := k.clientset.CoreV1().RESTClient().Post().Resource(&quot;pods&quot;).Name(args.ContainerId).Namespace(k.namespace).SubResource(&quot;exec&quot;).Param(&quot;container&quot;, CONTAINER_NAME)
    scheme := runtime.NewScheme()
    if err := v1.AddToScheme(scheme); err != nil {
        return &quot;&quot;, 0, fmt.Errorf(&quot;could not add to scheme: %w&quot;, err)
    }
    parameterCodec := runtime.NewParameterCodec(scheme)
    req.VersionedParams(&amp;v1.PodExecOptions{
        Stdin:     false,
        Stdout:    true,
        Stderr:    true,
        TTY:       false,
        Container: args.ContainerId,
        Command:   []string{&quot;sh&quot;, &quot;-c&quot;, args.Command},
    }, parameterCodec)
    exec, err := remotecommand.NewSPDYExecutor(k.config, &quot;POST&quot;, req.URL())
    if err != nil {
        return &quot;&quot;, 0, fmt.Errorf(&quot;could not exec command: %w&quot;, err)
    }
    var stdout, stderr bytes.Buffer
    var streamErr error
    streamErr = exec.Stream(remotecommand.StreamOptions{
        Stdin:  nil,
        Stdout: &amp;stdout,
        Stderr: &amp;stderr,
        Tty:    false,
    })

    if streamErr != nil {
        if strings.Contains(streamErr.Error(), &quot;command terminated with exit code&quot;) {
            return stderr.String(), 1, nil
        } else {
            return &quot;&quot;, 0, fmt.Errorf(&quot;could not stream results: %w&quot;, streamErr)
        }
    }
    return stdout.String(), 0, nil
}
</code></pre>
<p>In this implementation I don't get to know the state of the remote command until it's finished executing at which point I get all of the output logs at once.</p>
<p>Is there a way to read <code>stdout</code>/<code>stderr</code> while they're being written by the call to <code>exec.Stream</code>? In an ideal world I'd like to be able to print the output of the remote command line by line. I noticed that <code>bytes.Buffer</code> has a <code>ReadString</code> method which accepts a delimiter. That looks like a useful method but I haven't been able to figure out how to use it.</p>

Paymahn Moghadasian
<p>This is only a partial answer but if I set use the following <code>PodExecOptions</code> and <code>StreamOptions</code> then I see each log line get printed in real time (note that <code>Tty</code> is <code>true</code> and I'm using stdin and stdout, not custom buffers):</p>
<pre class="lang-golang prettyprint-override"><code>v1.PodExecOptions{
        Stdin:     true,
        Stdout:    true,
        Stderr:    false,
        TTY:       true,
        Container: args.ContainerId,
        Command:   []string{&quot;sh&quot;, &quot;-c&quot;, args.Command},
    }
</code></pre>
<p>and</p>
<pre class="lang-golang prettyprint-override"><code>remotecommand.StreamOptions{
            Stdin:  os.Stdin,
            Stdout: os.Stdout,
            Stderr: nil,
            Tty:    true,
        }
</code></pre>
<p>However, if I try to use something other than <code>os.Stdin</code> and <code>os.Stdout</code> then I never get any log lines. For example, the following usage doesn't print anything:</p>
<pre class="lang-golang prettyprint-override"><code>    var stdout, stdin bytes.Buffer
    var streamErr error

    go func() {
        streamErr = exec.Stream(remotecommand.StreamOptions{
            Stdin:  &amp;stdin,
            Stdout: &amp;stdout,
            Stderr: nil,
            Tty:    true,
        })
    }()
    time.Sleep(5*time.Second)
    log.Info(&quot;doing raw string calls on both buffers&quot;)
    log.Info(stdin.String())
    log.Info(stdout.String())
    log.Info(&quot;starting scan of stdin&quot;)
    scanner := bufio.NewScanner(&amp;stdin)
    scanner.Split(bufio.ScanLines)
    for scanner.Scan() {
        m := scanner.Text()
        fmt.Println(m)
    }
    log.Info(&quot;starting scan of stdout&quot;)
    scanner = bufio.NewScanner(&amp;stdout)
    scanner.Split(bufio.ScanLines)
    for scanner.Scan() {
        m := scanner.Text()
        fmt.Println(m)
    }
    log.Info(&quot;finished scanning of stdout&quot;)
</code></pre>
<p>I'm still trying to figure out how to use custom buffers so I can manage what's written to my logs instead of piping directly to stdout (I want to attach some custom fields to each line that gets logged).</p>
<hr />
<p>EDIT: alright, I figured out a solution that works. Here's the full code</p>
<pre><code>type LogStreamer struct{
    b bytes.Buffer
}

func (l *LogStreamer) String() string {
    return l.b.String()
}

func (l *LogStreamer) Write(p []byte) (n int, err error) {
    a := strings.TrimSpace(string(p))
    l.b.WriteString(a)
    log.Info(a)
    return len(p), nil
}

func (k *KubernetesClient) RunCommand(ctx context.Context, args *RunCommandArgs) (string, int, error) {
    req := k.clientset.CoreV1().RESTClient().Post().Resource(&quot;pods&quot;).Name(args.ContainerId).Namespace(k.namespace).SubResource(&quot;exec&quot;).Param(&quot;container&quot;, &quot;worker&quot;)
    scheme := runtime.NewScheme()
    if err := v1.AddToScheme(scheme); err != nil {
        return &quot;&quot;, 0, fmt.Errorf(&quot;could not add to scheme: %w&quot;, err)
    }
    parameterCodec := runtime.NewParameterCodec(scheme)
    req.VersionedParams(&amp;v1.PodExecOptions{
        Stdin:     true,
        Stdout:    true,
        Stderr:    false,
        TTY:       true,
        Container: args.ContainerId,
        Command:   []string{&quot;sh&quot;, &quot;-c&quot;, args.Command},
    }, parameterCodec)
    exec, err := remotecommand.NewSPDYExecutor(k.config, &quot;POST&quot;, req.URL())
    if err != nil {
        return &quot;&quot;, 0, fmt.Errorf(&quot;could not exec command: %w&quot;, err)
    }
    var streamErr error
    l := &amp;LogStreamer{}

    streamErr = exec.Stream(remotecommand.StreamOptions{
        Stdin:  os.Stdin,
        Stdout: l,
        Stderr: nil,
        Tty:    true,
    })

    if streamErr != nil {
        if strings.Contains(streamErr.Error(), &quot;command terminated with exit code&quot;) {
            return l.String(), 1, nil
        } else {
            return &quot;&quot;, 0, fmt.Errorf(&quot;could not stream results: %w&quot;, streamErr)
        }
    }
    return l.String(), 0, nil
}
</code></pre>
<p>I created a struct which implements the <code>io.Writer</code> interface and use that in the <code>StreamOptions</code> struct. Also note that I <strong>had</strong> to use <code>os.Stdin</code> in the <code>StreamOptions</code> struct or else only a single line would be streamed back for <code>Stdout</code>.</p>
<p>Also note that I had to trim the buffer passed to <code>LogStreamer.Write</code> because it seems that carriage returns or newlines cause problems with the logrus package. There's still more polish to add to this solution but it's definitely headed in the right direction.</p>
