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 }