pkg/api/proxy/proxy.go (32 lines of code) (raw):
package proxy
import (
"errors"
"fmt"
"io"
"net"
"golang.org/x/sync/errgroup"
)
// Proxy connects the read and writer with the specified connection. Under typical usage the connection will be to the
// steps gRPC service listening on api.DefaultSocketPath, and the source and sink will be the stdin and stdout
// (respectively) of a streaming-text based protocol like `ssh` or `docker exec`.
//
// The proxy should exit immediately if either of the write or read loop finish. Whether proxying was complete and
// successful cannot be determined here and is up to the caller to determine based on the result of the data/operation
// being proxied.
func Proxy(source io.Reader, sink io.Writer, conn *net.UnixConn) error {
eg := errgroup.Group{}
// pipe source to the connection
eg.Go(func() (err error) {
defer func() {
err = errors.Join(err, conn.CloseRead()) // ensure the writing loop exits too...
}()
_, err = io.Copy(conn, source)
if err != nil {
return fmt.Errorf("proxying stdin to %q: %w", conn.RemoteAddr().String(), err)
}
return nil
})
// pipe the connection to sink
eg.Go(func() (err error) {
defer func() {
err = errors.Join(err, conn.CloseWrite()) // ensure the reading loop exists too...
}()
_, err = io.Copy(sink, conn)
if err != nil {
return fmt.Errorf("proxying %q to stdout: %w", conn.RemoteAddr().String(), err)
}
return nil
})
return eg.Wait()
}