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

     1  package local
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"sync/atomic"
     7  
     8  	"github.com/docker/buildx/build"
     9  	cbuild "github.com/docker/buildx/controller/build"
    10  	"github.com/docker/buildx/controller/control"
    11  	controllererrors "github.com/docker/buildx/controller/errdefs"
    12  	controllerapi "github.com/docker/buildx/controller/pb"
    13  	"github.com/docker/buildx/controller/processes"
    14  	"github.com/docker/buildx/util/ioset"
    15  	"github.com/docker/buildx/util/progress"
    16  	"github.com/docker/cli/cli/command"
    17  	"github.com/moby/buildkit/client"
    18  	"github.com/pkg/errors"
    19  )
    20  
    21  func NewLocalBuildxController(ctx context.Context, dockerCli command.Cli, logger progress.SubLogger) control.BuildxController {
    22  	return &localController{
    23  		dockerCli: dockerCli,
    24  		ref:       "local",
    25  		processes: processes.NewManager(),
    26  	}
    27  }
    28  
    29  type buildConfig struct {
    30  	// TODO: these two structs should be merged
    31  	// Discussion: https://github.com/docker/buildx/pull/1640#discussion_r1113279719
    32  	resultCtx    *build.ResultHandle
    33  	buildOptions *controllerapi.BuildOptions
    34  }
    35  
    36  type localController struct {
    37  	dockerCli   command.Cli
    38  	ref         string
    39  	buildConfig buildConfig
    40  	processes   *processes.Manager
    41  
    42  	buildOnGoing atomic.Bool
    43  }
    44  
    45  func (b *localController) Build(ctx context.Context, options controllerapi.BuildOptions, in io.ReadCloser, progress progress.Writer) (string, *client.SolveResponse, error) {
    46  	if !b.buildOnGoing.CompareAndSwap(false, true) {
    47  		return "", nil, errors.New("build ongoing")
    48  	}
    49  	defer b.buildOnGoing.Store(false)
    50  
    51  	resp, res, buildErr := cbuild.RunBuild(ctx, b.dockerCli, options, in, progress, true)
    52  	// NOTE: RunBuild can return *build.ResultHandle even on error.
    53  	if res != nil {
    54  		b.buildConfig = buildConfig{
    55  			resultCtx:    res,
    56  			buildOptions: &options,
    57  		}
    58  		if buildErr != nil {
    59  			buildErr = controllererrors.WrapBuild(buildErr, b.ref)
    60  		}
    61  	}
    62  	if buildErr != nil {
    63  		return "", nil, buildErr
    64  	}
    65  	return b.ref, resp, nil
    66  }
    67  
    68  func (b *localController) ListProcesses(ctx context.Context, ref string) (infos []*controllerapi.ProcessInfo, retErr error) {
    69  	if ref != b.ref {
    70  		return nil, errors.Errorf("unknown ref %q", ref)
    71  	}
    72  	return b.processes.ListProcesses(), nil
    73  }
    74  
    75  func (b *localController) DisconnectProcess(ctx context.Context, ref, pid string) error {
    76  	if ref != b.ref {
    77  		return errors.Errorf("unknown ref %q", ref)
    78  	}
    79  	return b.processes.DeleteProcess(pid)
    80  }
    81  
    82  func (b *localController) cancelRunningProcesses() {
    83  	b.processes.CancelRunningProcesses()
    84  }
    85  
    86  func (b *localController) Invoke(ctx context.Context, ref string, pid string, cfg controllerapi.InvokeConfig, ioIn io.ReadCloser, ioOut io.WriteCloser, ioErr io.WriteCloser) error {
    87  	if ref != b.ref {
    88  		return errors.Errorf("unknown ref %q", ref)
    89  	}
    90  
    91  	proc, ok := b.processes.Get(pid)
    92  	if !ok {
    93  		// Start a new process.
    94  		if b.buildConfig.resultCtx == nil {
    95  			return errors.New("no build result is registered")
    96  		}
    97  		var err error
    98  		proc, err = b.processes.StartProcess(pid, b.buildConfig.resultCtx, &cfg)
    99  		if err != nil {
   100  			return err
   101  		}
   102  	}
   103  
   104  	// Attach containerIn to this process
   105  	ioCancelledCh := make(chan struct{})
   106  	proc.ForwardIO(&ioset.In{Stdin: ioIn, Stdout: ioOut, Stderr: ioErr}, func() { close(ioCancelledCh) })
   107  
   108  	select {
   109  	case <-ioCancelledCh:
   110  		return errors.Errorf("io cancelled")
   111  	case err := <-proc.Done():
   112  		return err
   113  	case <-ctx.Done():
   114  		return ctx.Err()
   115  	}
   116  }
   117  
   118  func (b *localController) Kill(context.Context) error {
   119  	b.Close()
   120  	return nil
   121  }
   122  
   123  func (b *localController) Close() error {
   124  	b.cancelRunningProcesses()
   125  	if b.buildConfig.resultCtx != nil {
   126  		b.buildConfig.resultCtx.Done()
   127  	}
   128  	// TODO: cancel ongoing builds?
   129  	return nil
   130  }
   131  
   132  func (b *localController) List(ctx context.Context) (res []string, _ error) {
   133  	return []string{b.ref}, nil
   134  }
   135  
   136  func (b *localController) Disconnect(ctx context.Context, key string) error {
   137  	b.Close()
   138  	return nil
   139  }
   140  
   141  func (b *localController) Inspect(ctx context.Context, ref string) (*controllerapi.InspectResponse, error) {
   142  	if ref != b.ref {
   143  		return nil, errors.Errorf("unknown ref %q", ref)
   144  	}
   145  	return &controllerapi.InspectResponse{Options: b.buildConfig.buildOptions}, nil
   146  }