Removes all references to the controller and moves the remaining sections of code to other packages. Processes has been moved to monitor where it is used and the data structs have been removed so buildflags is used directly. The controller build function has been moved to the commands package. Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
168 lines
3.8 KiB
Go
168 lines
3.8 KiB
Go
package build
|
|
|
|
import (
|
|
"context"
|
|
_ "crypto/sha256" // ensure digests can be computed
|
|
"io"
|
|
"sync"
|
|
"sync/atomic"
|
|
"syscall"
|
|
|
|
gateway "github.com/moby/buildkit/frontend/gateway/client"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type InvokeConfig struct {
|
|
Entrypoint []string
|
|
Cmd []string
|
|
NoCmd bool
|
|
Env []string
|
|
User string
|
|
NoUser bool
|
|
Cwd string
|
|
NoCwd bool
|
|
Tty bool
|
|
Rollback bool
|
|
Initial bool
|
|
SuspendOn SuspendOn
|
|
}
|
|
|
|
func (cfg *InvokeConfig) NeedsDebug(err error) bool {
|
|
return cfg.SuspendOn.DebugEnabled(err)
|
|
}
|
|
|
|
type SuspendOn int
|
|
|
|
const (
|
|
SuspendError SuspendOn = iota
|
|
SuspendAlways
|
|
)
|
|
|
|
func (s SuspendOn) DebugEnabled(err error) bool {
|
|
return err != nil || s == SuspendAlways
|
|
}
|
|
|
|
type Container struct {
|
|
cancelOnce sync.Once
|
|
containerCancel func(error)
|
|
isUnavailable atomic.Bool
|
|
initStarted atomic.Bool
|
|
container gateway.Container
|
|
releaseCh chan struct{}
|
|
resultCtx *ResultHandle
|
|
}
|
|
|
|
func NewContainer(ctx context.Context, resultCtx *ResultHandle, cfg *InvokeConfig) (*Container, error) {
|
|
mainCtx := ctx
|
|
|
|
ctrCh := make(chan *Container)
|
|
errCh := make(chan error)
|
|
go func() {
|
|
err := resultCtx.build(func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
|
|
ctx, cancel := context.WithCancelCause(ctx)
|
|
go func() {
|
|
<-mainCtx.Done()
|
|
cancel(errors.WithStack(context.Canceled))
|
|
}()
|
|
|
|
containerCfg, err := resultCtx.getContainerConfig(cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
containerCtx, containerCancel := context.WithCancelCause(ctx)
|
|
defer containerCancel(errors.WithStack(context.Canceled))
|
|
bkContainer, err := c.NewContainer(containerCtx, containerCfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
releaseCh := make(chan struct{})
|
|
container := &Container{
|
|
containerCancel: containerCancel,
|
|
container: bkContainer,
|
|
releaseCh: releaseCh,
|
|
resultCtx: resultCtx,
|
|
}
|
|
doneCh := make(chan struct{})
|
|
defer close(doneCh)
|
|
resultCtx.registerCleanup(func() {
|
|
container.Cancel()
|
|
<-doneCh
|
|
})
|
|
ctrCh <- container
|
|
<-container.releaseCh
|
|
|
|
return nil, bkContainer.Release(ctx)
|
|
})
|
|
if err != nil {
|
|
errCh <- err
|
|
}
|
|
}()
|
|
select {
|
|
case ctr := <-ctrCh:
|
|
return ctr, nil
|
|
case err := <-errCh:
|
|
return nil, err
|
|
case <-mainCtx.Done():
|
|
return nil, mainCtx.Err()
|
|
}
|
|
}
|
|
|
|
func (c *Container) Cancel() {
|
|
c.markUnavailable()
|
|
c.cancelOnce.Do(func() {
|
|
if c.containerCancel != nil {
|
|
c.containerCancel(errors.WithStack(context.Canceled))
|
|
}
|
|
close(c.releaseCh)
|
|
})
|
|
}
|
|
|
|
func (c *Container) IsUnavailable() bool {
|
|
return c.isUnavailable.Load()
|
|
}
|
|
|
|
func (c *Container) markUnavailable() {
|
|
c.isUnavailable.Store(true)
|
|
}
|
|
|
|
func (c *Container) Exec(ctx context.Context, cfg *InvokeConfig, stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) error {
|
|
if isInit := c.initStarted.CompareAndSwap(false, true); isInit {
|
|
defer func() {
|
|
// container can't be used after init exits
|
|
c.markUnavailable()
|
|
}()
|
|
}
|
|
err := exec(ctx, c.resultCtx, cfg, c.container, stdin, stdout, stderr)
|
|
if err != nil {
|
|
// Container becomes unavailable if one of the processes fails in it.
|
|
c.markUnavailable()
|
|
}
|
|
return err
|
|
}
|
|
|
|
func exec(ctx context.Context, resultCtx *ResultHandle, cfg *InvokeConfig, ctr gateway.Container, stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) error {
|
|
processCfg, err := resultCtx.getProcessConfig(cfg, stdin, stdout, stderr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
proc, err := ctr.Start(ctx, processCfg)
|
|
if err != nil {
|
|
return errors.Errorf("failed to start container: %v", err)
|
|
}
|
|
|
|
doneCh := make(chan struct{})
|
|
defer close(doneCh)
|
|
go func() {
|
|
select {
|
|
case <-ctx.Done():
|
|
if err := proc.Signal(ctx, syscall.SIGKILL); err != nil {
|
|
logrus.Warnf("failed to kill process: %v", err)
|
|
}
|
|
case <-doneCh:
|
|
}
|
|
}()
|
|
|
|
return proc.Wait()
|
|
}
|