github.com/pdmccormick/importable-docker-buildx@v0.0.0-20240426161518-e47091289030/controller/remote/client.go (about)

     1  package remote
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/containerd/containerd/defaults"
    10  	"github.com/containerd/containerd/pkg/dialer"
    11  	"github.com/docker/buildx/controller/pb"
    12  	"github.com/docker/buildx/util/progress"
    13  	"github.com/moby/buildkit/client"
    14  	"github.com/moby/buildkit/identity"
    15  	"github.com/moby/buildkit/util/grpcerrors"
    16  	"github.com/pkg/errors"
    17  	"golang.org/x/sync/errgroup"
    18  	"google.golang.org/grpc"
    19  	"google.golang.org/grpc/backoff"
    20  	"google.golang.org/grpc/credentials/insecure"
    21  )
    22  
    23  func NewClient(ctx context.Context, addr string) (*Client, error) {
    24  	backoffConfig := backoff.DefaultConfig
    25  	backoffConfig.MaxDelay = 3 * time.Second
    26  	connParams := grpc.ConnectParams{
    27  		Backoff: backoffConfig,
    28  	}
    29  	gopts := []grpc.DialOption{
    30  		grpc.WithBlock(),
    31  		grpc.WithTransportCredentials(insecure.NewCredentials()),
    32  		grpc.WithConnectParams(connParams),
    33  		grpc.WithContextDialer(dialer.ContextDialer),
    34  		grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(defaults.DefaultMaxRecvMsgSize)),
    35  		grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(defaults.DefaultMaxSendMsgSize)),
    36  		grpc.WithUnaryInterceptor(grpcerrors.UnaryClientInterceptor),
    37  		grpc.WithStreamInterceptor(grpcerrors.StreamClientInterceptor),
    38  	}
    39  	conn, err := grpc.DialContext(ctx, dialer.DialAddress(addr), gopts...)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  	return &Client{conn: conn}, nil
    44  }
    45  
    46  type Client struct {
    47  	conn      *grpc.ClientConn
    48  	closeOnce sync.Once
    49  }
    50  
    51  func (c *Client) Close() (err error) {
    52  	c.closeOnce.Do(func() {
    53  		err = c.conn.Close()
    54  	})
    55  	return
    56  }
    57  
    58  func (c *Client) Version(ctx context.Context) (string, string, string, error) {
    59  	res, err := c.client().Info(ctx, &pb.InfoRequest{})
    60  	if err != nil {
    61  		return "", "", "", err
    62  	}
    63  	v := res.BuildxVersion
    64  	return v.Package, v.Version, v.Revision, nil
    65  }
    66  
    67  func (c *Client) List(ctx context.Context) (keys []string, retErr error) {
    68  	res, err := c.client().List(ctx, &pb.ListRequest{})
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	return res.Keys, nil
    73  }
    74  
    75  func (c *Client) Disconnect(ctx context.Context, key string) error {
    76  	if key == "" {
    77  		return nil
    78  	}
    79  	_, err := c.client().Disconnect(ctx, &pb.DisconnectRequest{Ref: key})
    80  	return err
    81  }
    82  
    83  func (c *Client) ListProcesses(ctx context.Context, ref string) (infos []*pb.ProcessInfo, retErr error) {
    84  	res, err := c.client().ListProcesses(ctx, &pb.ListProcessesRequest{Ref: ref})
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	return res.Infos, nil
    89  }
    90  
    91  func (c *Client) DisconnectProcess(ctx context.Context, ref, pid string) error {
    92  	_, err := c.client().DisconnectProcess(ctx, &pb.DisconnectProcessRequest{Ref: ref, ProcessID: pid})
    93  	return err
    94  }
    95  
    96  func (c *Client) Invoke(ctx context.Context, ref string, pid string, invokeConfig pb.InvokeConfig, in io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) error {
    97  	if ref == "" || pid == "" {
    98  		return errors.New("build reference must be specified")
    99  	}
   100  	stream, err := c.client().Invoke(ctx)
   101  	if err != nil {
   102  		return err
   103  	}
   104  	return attachIO(ctx, stream, &pb.InitMessage{Ref: ref, ProcessID: pid, InvokeConfig: &invokeConfig}, ioAttachConfig{
   105  		stdin:  in,
   106  		stdout: stdout,
   107  		stderr: stderr,
   108  		// TODO: Signal, Resize
   109  	})
   110  }
   111  
   112  func (c *Client) Inspect(ctx context.Context, ref string) (*pb.InspectResponse, error) {
   113  	return c.client().Inspect(ctx, &pb.InspectRequest{Ref: ref})
   114  }
   115  
   116  func (c *Client) Build(ctx context.Context, options pb.BuildOptions, in io.ReadCloser, progress progress.Writer) (string, *client.SolveResponse, error) {
   117  	ref := identity.NewID()
   118  	statusChan := make(chan *client.SolveStatus)
   119  	eg, egCtx := errgroup.WithContext(ctx)
   120  	var resp *client.SolveResponse
   121  	eg.Go(func() error {
   122  		defer close(statusChan)
   123  		var err error
   124  		resp, err = c.build(egCtx, ref, options, in, statusChan)
   125  		return err
   126  	})
   127  	eg.Go(func() error {
   128  		for s := range statusChan {
   129  			st := s
   130  			progress.Write(st)
   131  		}
   132  		return nil
   133  	})
   134  	return ref, resp, eg.Wait()
   135  }
   136  
   137  func (c *Client) build(ctx context.Context, ref string, options pb.BuildOptions, in io.ReadCloser, statusChan chan *client.SolveStatus) (*client.SolveResponse, error) {
   138  	eg, egCtx := errgroup.WithContext(ctx)
   139  	done := make(chan struct{})
   140  
   141  	var resp *client.SolveResponse
   142  
   143  	eg.Go(func() error {
   144  		defer close(done)
   145  		pbResp, err := c.client().Build(egCtx, &pb.BuildRequest{
   146  			Ref:     ref,
   147  			Options: &options,
   148  		})
   149  		if err != nil {
   150  			return err
   151  		}
   152  		resp = &client.SolveResponse{
   153  			ExporterResponse: pbResp.ExporterResponse,
   154  		}
   155  		return nil
   156  	})
   157  	eg.Go(func() error {
   158  		stream, err := c.client().Status(egCtx, &pb.StatusRequest{
   159  			Ref: ref,
   160  		})
   161  		if err != nil {
   162  			return err
   163  		}
   164  		for {
   165  			resp, err := stream.Recv()
   166  			if err != nil {
   167  				if err == io.EOF {
   168  					return nil
   169  				}
   170  				return errors.Wrap(err, "failed to receive status")
   171  			}
   172  			statusChan <- pb.FromControlStatus(resp)
   173  		}
   174  	})
   175  	if in != nil {
   176  		eg.Go(func() error {
   177  			stream, err := c.client().Input(egCtx)
   178  			if err != nil {
   179  				return err
   180  			}
   181  			if err := stream.Send(&pb.InputMessage{
   182  				Input: &pb.InputMessage_Init{
   183  					Init: &pb.InputInitMessage{
   184  						Ref: ref,
   185  					},
   186  				},
   187  			}); err != nil {
   188  				return errors.Wrap(err, "failed to init input")
   189  			}
   190  
   191  			inReader, inWriter := io.Pipe()
   192  			eg2, _ := errgroup.WithContext(ctx)
   193  			eg2.Go(func() error {
   194  				<-done
   195  				return inWriter.Close()
   196  			})
   197  			go func() {
   198  				// do not wait for read completion but return here and let the caller send EOF
   199  				// this allows us to return on ctx.Done() without being blocked by this reader.
   200  				io.Copy(inWriter, in)
   201  				inWriter.Close()
   202  			}()
   203  			eg2.Go(func() error {
   204  				for {
   205  					buf := make([]byte, 32*1024)
   206  					n, err := inReader.Read(buf)
   207  					if err != nil {
   208  						if err == io.EOF {
   209  							break // break loop and send EOF
   210  						}
   211  						return err
   212  					} else if n > 0 {
   213  						if err := stream.Send(&pb.InputMessage{
   214  							Input: &pb.InputMessage_Data{
   215  								Data: &pb.DataMessage{
   216  									Data: buf[:n],
   217  								},
   218  							},
   219  						}); err != nil {
   220  							return err
   221  						}
   222  					}
   223  				}
   224  				return stream.Send(&pb.InputMessage{
   225  					Input: &pb.InputMessage_Data{
   226  						Data: &pb.DataMessage{
   227  							EOF: true,
   228  						},
   229  					},
   230  				})
   231  			})
   232  			return eg2.Wait()
   233  		})
   234  	}
   235  	return resp, eg.Wait()
   236  }
   237  
   238  func (c *Client) client() pb.ControllerClient {
   239  	return pb.NewControllerClient(c.conn)
   240  }