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.
		
		
		
		
		
			
		
			
				
	
	
		
			195 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
			
		
		
	
	
			195 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
| package dockerui
 | |
| 
 | |
| import (
 | |
| 	"archive/tar"
 | |
| 	"bytes"
 | |
| 	"context"
 | |
| 	"path/filepath"
 | |
| 	"regexp"
 | |
| 	"strconv"
 | |
| 
 | |
| 	"github.com/moby/buildkit/client/llb"
 | |
| 	"github.com/moby/buildkit/frontend/gateway/client"
 | |
| 	gwpb "github.com/moby/buildkit/frontend/gateway/pb"
 | |
| 	"github.com/moby/buildkit/util/gitutil"
 | |
| 	"github.com/pkg/errors"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	DefaultLocalNameContext    = "context"
 | |
| 	DefaultLocalNameDockerfile = "dockerfile"
 | |
| 	DefaultDockerfileName      = "Dockerfile"
 | |
| 	DefaultDockerignoreName    = ".dockerignore"
 | |
| 	EmptyImageName             = "scratch"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	keyFilename       = "filename"
 | |
| 	keyContextSubDir  = "contextsubdir"
 | |
| 	keyNameContext    = "contextkey"
 | |
| 	keyNameDockerfile = "dockerfilekey"
 | |
| )
 | |
| 
 | |
| var httpPrefix = regexp.MustCompile(`^https?://`)
 | |
| 
 | |
| type buildContext struct {
 | |
| 	context              *llb.State // set if not local
 | |
| 	dockerfile           *llb.State // override remoteContext if set
 | |
| 	contextLocalName     string
 | |
| 	dockerfileLocalName  string
 | |
| 	filename             string
 | |
| 	forceLocalDockerfile bool
 | |
| }
 | |
| 
 | |
| func (bc *Client) marshalOpts() []llb.ConstraintsOpt {
 | |
| 	return []llb.ConstraintsOpt{llb.WithCaps(bc.bopts.Caps)}
 | |
| }
 | |
| 
 | |
| func (bc *Client) initContext(ctx context.Context) (*buildContext, error) {
 | |
| 	opts := bc.bopts.Opts
 | |
| 	gwcaps := bc.bopts.Caps
 | |
| 
 | |
| 	localNameContext := DefaultLocalNameContext
 | |
| 	if v, ok := opts[keyNameContext]; ok {
 | |
| 		localNameContext = v
 | |
| 	}
 | |
| 
 | |
| 	bctx := &buildContext{
 | |
| 		contextLocalName:    DefaultLocalNameContext,
 | |
| 		dockerfileLocalName: DefaultLocalNameDockerfile,
 | |
| 		filename:            DefaultDockerfileName,
 | |
| 	}
 | |
| 
 | |
| 	if v, ok := opts[keyFilename]; ok {
 | |
| 		bctx.filename = v
 | |
| 	}
 | |
| 
 | |
| 	if v, ok := opts[keyNameDockerfile]; ok {
 | |
| 		bctx.forceLocalDockerfile = true
 | |
| 		bctx.dockerfileLocalName = v
 | |
| 	}
 | |
| 
 | |
| 	keepGit := false
 | |
| 	if v, err := strconv.ParseBool(opts[keyContextKeepGitDirArg]); err == nil {
 | |
| 		keepGit = v
 | |
| 	}
 | |
| 	if st, ok := DetectGitContext(opts[localNameContext], keepGit); ok {
 | |
| 		bctx.context = st
 | |
| 		bctx.dockerfile = st
 | |
| 	} else if st, filename, ok := DetectHTTPContext(opts[localNameContext]); ok {
 | |
| 		def, err := st.Marshal(ctx, bc.marshalOpts()...)
 | |
| 		if err != nil {
 | |
| 			return nil, errors.Wrapf(err, "failed to marshal httpcontext")
 | |
| 		}
 | |
| 		res, err := bc.client.Solve(ctx, client.SolveRequest{
 | |
| 			Definition: def.ToPB(),
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			return nil, errors.Wrapf(err, "failed to resolve httpcontext")
 | |
| 		}
 | |
| 
 | |
| 		ref, err := res.SingleRef()
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		dt, err := ref.ReadFile(ctx, client.ReadRequest{
 | |
| 			Filename: filename,
 | |
| 			Range: &client.FileRange{
 | |
| 				Length: 1024,
 | |
| 			},
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			return nil, errors.Wrapf(err, "failed to read downloaded context")
 | |
| 		}
 | |
| 		if isArchive(dt) {
 | |
| 			bc := llb.Scratch().File(llb.Copy(*st, filepath.Join("/", filename), "/", &llb.CopyInfo{
 | |
| 				AttemptUnpack: true,
 | |
| 			}))
 | |
| 			bctx.context = &bc
 | |
| 		} else {
 | |
| 			bctx.filename = filename
 | |
| 			bctx.context = st
 | |
| 		}
 | |
| 		bctx.dockerfile = bctx.context
 | |
| 	} else if (&gwcaps).Supports(gwpb.CapFrontendInputs) == nil {
 | |
| 		inputs, err := bc.client.Inputs(ctx)
 | |
| 		if err != nil {
 | |
| 			return nil, errors.Wrapf(err, "failed to get frontend inputs")
 | |
| 		}
 | |
| 
 | |
| 		if !bctx.forceLocalDockerfile {
 | |
| 			inputDockerfile, ok := inputs[bctx.dockerfileLocalName]
 | |
| 			if ok {
 | |
| 				bctx.dockerfile = &inputDockerfile
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		inputCtx, ok := inputs[DefaultLocalNameContext]
 | |
| 		if ok {
 | |
| 			bctx.context = &inputCtx
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if bctx.context != nil {
 | |
| 		if sub, ok := opts[keyContextSubDir]; ok {
 | |
| 			bctx.context = scopeToSubDir(bctx.context, sub)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return bctx, nil
 | |
| }
 | |
| 
 | |
| func DetectGitContext(ref string, keepGit bool) (*llb.State, bool) {
 | |
| 	g, err := gitutil.ParseGitRef(ref)
 | |
| 	if err != nil {
 | |
| 		return nil, false
 | |
| 	}
 | |
| 	commit := g.Commit
 | |
| 	if g.SubDir != "" {
 | |
| 		commit += ":" + g.SubDir
 | |
| 	}
 | |
| 	gitOpts := []llb.GitOption{WithInternalName("load git source " + ref)}
 | |
| 	if keepGit {
 | |
| 		gitOpts = append(gitOpts, llb.KeepGitDir())
 | |
| 	}
 | |
| 
 | |
| 	st := llb.Git(g.Remote, commit, gitOpts...)
 | |
| 	return &st, true
 | |
| }
 | |
| 
 | |
| func DetectHTTPContext(ref string) (*llb.State, string, bool) {
 | |
| 	filename := "context"
 | |
| 	if httpPrefix.MatchString(ref) {
 | |
| 		st := llb.HTTP(ref, llb.Filename(filename), WithInternalName("load remote build context"))
 | |
| 		return &st, filename, true
 | |
| 	}
 | |
| 	return nil, "", false
 | |
| }
 | |
| 
 | |
| func isArchive(header []byte) bool {
 | |
| 	for _, m := range [][]byte{
 | |
| 		{0x42, 0x5A, 0x68},                   // bzip2
 | |
| 		{0x1F, 0x8B, 0x08},                   // gzip
 | |
| 		{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, // xz
 | |
| 	} {
 | |
| 		if len(header) < len(m) {
 | |
| 			continue
 | |
| 		}
 | |
| 		if bytes.Equal(m, header[:len(m)]) {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	r := tar.NewReader(bytes.NewBuffer(header))
 | |
| 	_, err := r.Next()
 | |
| 	return err == nil
 | |
| }
 | |
| 
 | |
| func scopeToSubDir(c *llb.State, dir string) *llb.State {
 | |
| 	bc := llb.Scratch().File(llb.Copy(*c, dir, "/", &llb.CopyInfo{
 | |
| 		CopyDirContentsOnly: true,
 | |
| 	}))
 | |
| 	return &bc
 | |
| }
 |