You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			217 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
			
		
		
	
	
			217 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
package build
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	_ "crypto/sha256" // ensure digests can be computed
 | 
						|
	"encoding/json"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"sync"
 | 
						|
	"sync/atomic"
 | 
						|
	"syscall"
 | 
						|
 | 
						|
	controllerapi "github.com/docker/buildx/controller/pb"
 | 
						|
	"github.com/moby/buildkit/client"
 | 
						|
	"github.com/moby/buildkit/exporter/containerimage/exptypes"
 | 
						|
	gateway "github.com/moby/buildkit/frontend/gateway/client"
 | 
						|
	"github.com/moby/buildkit/solver/pb"
 | 
						|
	specs "github.com/opencontainers/image-spec/specs-go/v1"
 | 
						|
	"github.com/pkg/errors"
 | 
						|
	"github.com/sirupsen/logrus"
 | 
						|
)
 | 
						|
 | 
						|
// ResultContext is a build result with the client that built it.
 | 
						|
type ResultContext struct {
 | 
						|
	Client *client.Client
 | 
						|
	Res    *gateway.Result
 | 
						|
}
 | 
						|
 | 
						|
type Container struct {
 | 
						|
	cancelOnce      sync.Once
 | 
						|
	containerCancel func()
 | 
						|
 | 
						|
	isUnavailable atomic.Bool
 | 
						|
 | 
						|
	initStarted atomic.Bool
 | 
						|
 | 
						|
	container gateway.Container
 | 
						|
	image     *specs.Image
 | 
						|
 | 
						|
	releaseCh chan struct{}
 | 
						|
}
 | 
						|
 | 
						|
func NewContainer(ctx context.Context, resultCtx *ResultContext) (*Container, error) {
 | 
						|
	c, res := resultCtx.Client, resultCtx.Res
 | 
						|
 | 
						|
	mainCtx := ctx
 | 
						|
 | 
						|
	ctrCh := make(chan *Container)
 | 
						|
	errCh := make(chan error)
 | 
						|
	go func() {
 | 
						|
		_, err := c.Build(context.TODO(), client.SolveOpt{}, "buildx", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
 | 
						|
			ctx, cancel := context.WithCancel(ctx)
 | 
						|
			go func() {
 | 
						|
				<-mainCtx.Done()
 | 
						|
				cancel()
 | 
						|
			}()
 | 
						|
 | 
						|
			if res.Ref == nil {
 | 
						|
				return nil, errors.Errorf("no reference is registered")
 | 
						|
			}
 | 
						|
			st, err := res.Ref.ToState()
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
			def, err := st.Marshal(ctx)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
			imgRef, err := c.Solve(ctx, gateway.SolveRequest{
 | 
						|
				Definition: def.ToPB(),
 | 
						|
			})
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
			containerCtx, containerCancel := context.WithCancel(ctx)
 | 
						|
			defer containerCancel()
 | 
						|
			bkContainer, err := c.NewContainer(containerCtx, gateway.NewContainerRequest{
 | 
						|
				Mounts: []gateway.Mount{
 | 
						|
					{
 | 
						|
						Dest:      "/",
 | 
						|
						MountType: pb.MountType_BIND,
 | 
						|
						Ref:       imgRef.Ref,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			})
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
			imgData := res.Metadata[exptypes.ExporterImageConfigKey]
 | 
						|
			var img *specs.Image
 | 
						|
			if len(imgData) > 0 {
 | 
						|
				img = &specs.Image{}
 | 
						|
				if err := json.Unmarshal(imgData, img); err != nil {
 | 
						|
					fmt.Println(err)
 | 
						|
					return nil, err
 | 
						|
				}
 | 
						|
			}
 | 
						|
			releaseCh := make(chan struct{})
 | 
						|
			container := &Container{
 | 
						|
				containerCancel: containerCancel,
 | 
						|
				container:       bkContainer,
 | 
						|
				image:           img,
 | 
						|
				releaseCh:       releaseCh,
 | 
						|
			}
 | 
						|
			ctrCh <- container
 | 
						|
			<-container.releaseCh
 | 
						|
 | 
						|
			return nil, bkContainer.Release(ctx)
 | 
						|
		}, nil)
 | 
						|
		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()
 | 
						|
		}
 | 
						|
		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 *controllerapi.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, cfg, c.container, c.image, 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, cfg *controllerapi.InvokeConfig, ctr gateway.Container, img *specs.Image, stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) error {
 | 
						|
	user := ""
 | 
						|
	if !cfg.NoUser {
 | 
						|
		user = cfg.User
 | 
						|
	} else if img != nil {
 | 
						|
		user = img.Config.User
 | 
						|
	}
 | 
						|
 | 
						|
	cwd := ""
 | 
						|
	if !cfg.NoCwd {
 | 
						|
		cwd = cfg.Cwd
 | 
						|
	} else if img != nil {
 | 
						|
		cwd = img.Config.WorkingDir
 | 
						|
	}
 | 
						|
 | 
						|
	env := []string{}
 | 
						|
	if img != nil {
 | 
						|
		env = append(env, img.Config.Env...)
 | 
						|
	}
 | 
						|
	env = append(env, cfg.Env...)
 | 
						|
 | 
						|
	args := []string{}
 | 
						|
	if cfg.Entrypoint != nil {
 | 
						|
		args = append(args, cfg.Entrypoint...)
 | 
						|
	}
 | 
						|
	if cfg.Cmd != nil {
 | 
						|
		args = append(args, cfg.Cmd...)
 | 
						|
	}
 | 
						|
	// caller should always set args
 | 
						|
	if len(args) == 0 {
 | 
						|
		return errors.Errorf("specify args to execute")
 | 
						|
	}
 | 
						|
	proc, err := ctr.Start(ctx, gateway.StartRequest{
 | 
						|
		Args:   args,
 | 
						|
		Env:    env,
 | 
						|
		User:   user,
 | 
						|
		Cwd:    cwd,
 | 
						|
		Tty:    cfg.Tty,
 | 
						|
		Stdin:  stdin,
 | 
						|
		Stdout: stdout,
 | 
						|
		Stderr: stderr,
 | 
						|
	})
 | 
						|
	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()
 | 
						|
}
 |