mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 01:53:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			658 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			658 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package build
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"context"
 | 
						|
	"io"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"slices"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"syscall"
 | 
						|
 | 
						|
	"github.com/containerd/containerd/content"
 | 
						|
	"github.com/containerd/containerd/content/local"
 | 
						|
	"github.com/containerd/platforms"
 | 
						|
	"github.com/distribution/reference"
 | 
						|
	"github.com/docker/buildx/builder"
 | 
						|
	"github.com/docker/buildx/driver"
 | 
						|
	"github.com/docker/buildx/util/confutil"
 | 
						|
	"github.com/docker/buildx/util/dockerutil"
 | 
						|
	"github.com/docker/buildx/util/osutil"
 | 
						|
	"github.com/docker/buildx/util/progress"
 | 
						|
	"github.com/moby/buildkit/client"
 | 
						|
	"github.com/moby/buildkit/client/llb"
 | 
						|
	"github.com/moby/buildkit/client/ociindex"
 | 
						|
	gateway "github.com/moby/buildkit/frontend/gateway/client"
 | 
						|
	"github.com/moby/buildkit/identity"
 | 
						|
	"github.com/moby/buildkit/session/upload/uploadprovider"
 | 
						|
	"github.com/moby/buildkit/solver/pb"
 | 
						|
	"github.com/moby/buildkit/util/apicaps"
 | 
						|
	"github.com/moby/buildkit/util/entitlements"
 | 
						|
	"github.com/opencontainers/go-digest"
 | 
						|
	"github.com/pkg/errors"
 | 
						|
	"github.com/tonistiigi/fsutil"
 | 
						|
)
 | 
						|
 | 
						|
func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt *Options, bopts gateway.BuildOpts, cfg *confutil.Config, pw progress.Writer, docker *dockerutil.Client) (_ *client.SolveOpt, release func(), err error) {
 | 
						|
	nodeDriver := node.Driver
 | 
						|
	defers := make([]func(), 0, 2)
 | 
						|
	releaseF := func() {
 | 
						|
		for _, f := range defers {
 | 
						|
			f()
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	defer func() {
 | 
						|
		if err != nil {
 | 
						|
			releaseF()
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	// inline cache from build arg
 | 
						|
	if v, ok := opt.BuildArgs["BUILDKIT_INLINE_CACHE"]; ok {
 | 
						|
		if v, _ := strconv.ParseBool(v); v {
 | 
						|
			opt.CacheTo = append(opt.CacheTo, client.CacheOptionsEntry{
 | 
						|
				Type:  "inline",
 | 
						|
				Attrs: map[string]string{},
 | 
						|
			})
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for _, e := range opt.CacheTo {
 | 
						|
		if e.Type != "inline" && !nodeDriver.Features(ctx)[driver.CacheExport] {
 | 
						|
			return nil, nil, notSupported(driver.CacheExport, nodeDriver, "https://docs.docker.com/go/build-cache-backends/")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	cacheTo := make([]client.CacheOptionsEntry, 0, len(opt.CacheTo))
 | 
						|
	for _, e := range opt.CacheTo {
 | 
						|
		if e.Type == "gha" {
 | 
						|
			if !bopts.LLBCaps.Contains(apicaps.CapID("cache.gha")) {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
		} else if e.Type == "s3" {
 | 
						|
			if !bopts.LLBCaps.Contains(apicaps.CapID("cache.s3")) {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
		}
 | 
						|
		cacheTo = append(cacheTo, e)
 | 
						|
	}
 | 
						|
 | 
						|
	cacheFrom := make([]client.CacheOptionsEntry, 0, len(opt.CacheFrom))
 | 
						|
	for _, e := range opt.CacheFrom {
 | 
						|
		if e.Type == "gha" {
 | 
						|
			if !bopts.LLBCaps.Contains(apicaps.CapID("cache.gha")) {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
		} else if e.Type == "s3" {
 | 
						|
			if !bopts.LLBCaps.Contains(apicaps.CapID("cache.s3")) {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
		}
 | 
						|
		cacheFrom = append(cacheFrom, e)
 | 
						|
	}
 | 
						|
 | 
						|
	so := client.SolveOpt{
 | 
						|
		Ref:                 opt.Ref,
 | 
						|
		Frontend:            "dockerfile.v0",
 | 
						|
		FrontendAttrs:       map[string]string{},
 | 
						|
		LocalMounts:         map[string]fsutil.FS{},
 | 
						|
		CacheExports:        cacheTo,
 | 
						|
		CacheImports:        cacheFrom,
 | 
						|
		AllowedEntitlements: opt.Allow,
 | 
						|
		SourcePolicy:        opt.SourcePolicy,
 | 
						|
	}
 | 
						|
 | 
						|
	if opt.CgroupParent != "" {
 | 
						|
		so.FrontendAttrs["cgroup-parent"] = opt.CgroupParent
 | 
						|
	}
 | 
						|
 | 
						|
	if v, ok := opt.BuildArgs["BUILDKIT_MULTI_PLATFORM"]; ok {
 | 
						|
		if v, _ := strconv.ParseBool(v); v {
 | 
						|
			so.FrontendAttrs["multi-platform"] = "true"
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if multiDriver {
 | 
						|
		// force creation of manifest list
 | 
						|
		so.FrontendAttrs["multi-platform"] = "true"
 | 
						|
	}
 | 
						|
 | 
						|
	attests := make(map[string]string)
 | 
						|
	for k, v := range opt.Attests {
 | 
						|
		if v != nil {
 | 
						|
			attests[k] = *v
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	supportAttestations := bopts.LLBCaps.Contains(apicaps.CapID("exporter.image.attestations")) && nodeDriver.Features(ctx)[driver.MultiPlatform]
 | 
						|
	if len(attests) > 0 {
 | 
						|
		if !supportAttestations {
 | 
						|
			if !nodeDriver.Features(ctx)[driver.MultiPlatform] {
 | 
						|
				return nil, nil, notSupported("Attestation", nodeDriver, "https://docs.docker.com/go/attestations/")
 | 
						|
			}
 | 
						|
			return nil, nil, errors.Errorf("Attestations are not supported by the current BuildKit daemon")
 | 
						|
		}
 | 
						|
		for k, v := range attests {
 | 
						|
			so.FrontendAttrs["attest:"+k] = v
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if _, ok := opt.Attests["provenance"]; !ok && supportAttestations {
 | 
						|
		const noAttestEnv = "BUILDX_NO_DEFAULT_ATTESTATIONS"
 | 
						|
		var noProv bool
 | 
						|
		if v, ok := os.LookupEnv(noAttestEnv); ok {
 | 
						|
			noProv, err = strconv.ParseBool(v)
 | 
						|
			if err != nil {
 | 
						|
				return nil, nil, errors.Wrap(err, "invalid "+noAttestEnv)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if !noProv {
 | 
						|
			so.FrontendAttrs["attest:provenance"] = "mode=min,inline-only=true"
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	switch len(opt.Exports) {
 | 
						|
	case 1:
 | 
						|
		// valid
 | 
						|
	case 0:
 | 
						|
		if !noDefaultLoad() && opt.CallFunc == nil {
 | 
						|
			if nodeDriver.IsMobyDriver() {
 | 
						|
				// backwards compat for docker driver only:
 | 
						|
				// this ensures the build results in a docker image.
 | 
						|
				opt.Exports = []client.ExportEntry{{Type: "image", Attrs: map[string]string{}}}
 | 
						|
			} else if nodeDriver.Features(ctx)[driver.DefaultLoad] {
 | 
						|
				opt.Exports = []client.ExportEntry{{Type: "docker", Attrs: map[string]string{}}}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	default:
 | 
						|
		if err := bopts.LLBCaps.Supports(pb.CapMultipleExporters); err != nil {
 | 
						|
			return nil, nil, errors.Errorf("multiple outputs currently unsupported by the current BuildKit daemon, please upgrade to version v0.13+ or use a single output")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// fill in image exporter names from tags
 | 
						|
	if len(opt.Tags) > 0 {
 | 
						|
		tags := make([]string, len(opt.Tags))
 | 
						|
		for i, tag := range opt.Tags {
 | 
						|
			ref, err := reference.Parse(tag)
 | 
						|
			if err != nil {
 | 
						|
				return nil, nil, errors.Wrapf(err, "invalid tag %q", tag)
 | 
						|
			}
 | 
						|
			tags[i] = ref.String()
 | 
						|
		}
 | 
						|
		for i, e := range opt.Exports {
 | 
						|
			switch e.Type {
 | 
						|
			case "image", "oci", "docker":
 | 
						|
				opt.Exports[i].Attrs["name"] = strings.Join(tags, ",")
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		for _, e := range opt.Exports {
 | 
						|
			if e.Type == "image" && e.Attrs["name"] == "" && e.Attrs["push"] != "" {
 | 
						|
				if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok {
 | 
						|
					return nil, nil, errors.Errorf("tag is needed when pushing to registry")
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// cacheonly is a fake exporter to opt out of default behaviors
 | 
						|
	exports := make([]client.ExportEntry, 0, len(opt.Exports))
 | 
						|
	for _, e := range opt.Exports {
 | 
						|
		if e.Type != "cacheonly" {
 | 
						|
			exports = append(exports, e)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	opt.Exports = exports
 | 
						|
 | 
						|
	// set up exporters
 | 
						|
	for i, e := range opt.Exports {
 | 
						|
		if e.Type == "oci" && !nodeDriver.Features(ctx)[driver.OCIExporter] {
 | 
						|
			return nil, nil, notSupported(driver.OCIExporter, nodeDriver, "https://docs.docker.com/go/build-exporters/")
 | 
						|
		}
 | 
						|
		if e.Type == "docker" {
 | 
						|
			features := docker.Features(ctx, e.Attrs["context"])
 | 
						|
			if features[dockerutil.OCIImporter] && e.Output == nil {
 | 
						|
				// rely on oci importer if available (which supports
 | 
						|
				// multi-platform images), otherwise fall back to docker
 | 
						|
				opt.Exports[i].Type = "oci"
 | 
						|
			} else if len(opt.Platforms) > 1 || len(attests) > 0 {
 | 
						|
				if e.Output != nil {
 | 
						|
					return nil, nil, errors.Errorf("docker exporter does not support exporting manifest lists, use the oci exporter instead")
 | 
						|
				}
 | 
						|
				return nil, nil, errors.Errorf("docker exporter does not currently support exporting manifest lists")
 | 
						|
			}
 | 
						|
			if e.Output == nil {
 | 
						|
				if nodeDriver.IsMobyDriver() {
 | 
						|
					e.Type = "image"
 | 
						|
				} else {
 | 
						|
					w, cancel, err := docker.LoadImage(ctx, e.Attrs["context"], pw)
 | 
						|
					if err != nil {
 | 
						|
						return nil, nil, err
 | 
						|
					}
 | 
						|
					defers = append(defers, cancel)
 | 
						|
					opt.Exports[i].Output = func(_ map[string]string) (io.WriteCloser, error) {
 | 
						|
						return w, nil
 | 
						|
					}
 | 
						|
				}
 | 
						|
			} else if !nodeDriver.Features(ctx)[driver.DockerExporter] {
 | 
						|
				return nil, nil, notSupported(driver.DockerExporter, nodeDriver, "https://docs.docker.com/go/build-exporters/")
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if e.Type == "image" && nodeDriver.IsMobyDriver() {
 | 
						|
			opt.Exports[i].Type = "moby"
 | 
						|
			if e.Attrs["push"] != "" {
 | 
						|
				if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok {
 | 
						|
					if ok, _ := strconv.ParseBool(e.Attrs["push-by-digest"]); ok {
 | 
						|
						return nil, nil, errors.Errorf("push-by-digest is currently not implemented for docker driver, please create a new builder instance")
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if e.Type == "docker" || e.Type == "image" || e.Type == "oci" {
 | 
						|
			// inline buildinfo attrs from build arg
 | 
						|
			if v, ok := opt.BuildArgs["BUILDKIT_INLINE_BUILDINFO_ATTRS"]; ok {
 | 
						|
				opt.Exports[i].Attrs["buildinfo-attrs"] = v
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	so.Exports = opt.Exports
 | 
						|
	so.Session = slices.Clone(opt.Session)
 | 
						|
 | 
						|
	releaseLoad, err := loadInputs(ctx, nodeDriver, &opt.Inputs, pw, &so)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, err
 | 
						|
	}
 | 
						|
	defers = append(defers, releaseLoad)
 | 
						|
 | 
						|
	// add node identifier to shared key if one was specified
 | 
						|
	if so.SharedKey != "" {
 | 
						|
		so.SharedKey += ":" + cfg.TryNodeIdentifier()
 | 
						|
	}
 | 
						|
 | 
						|
	if opt.Pull {
 | 
						|
		so.FrontendAttrs["image-resolve-mode"] = pb.AttrImageResolveModeForcePull
 | 
						|
	} else if nodeDriver.IsMobyDriver() {
 | 
						|
		// moby driver always resolves local images by default
 | 
						|
		so.FrontendAttrs["image-resolve-mode"] = pb.AttrImageResolveModePreferLocal
 | 
						|
	}
 | 
						|
	if opt.Target != "" {
 | 
						|
		so.FrontendAttrs["target"] = opt.Target
 | 
						|
	}
 | 
						|
	if len(opt.NoCacheFilter) > 0 {
 | 
						|
		so.FrontendAttrs["no-cache"] = strings.Join(opt.NoCacheFilter, ",")
 | 
						|
	}
 | 
						|
	if opt.NoCache {
 | 
						|
		so.FrontendAttrs["no-cache"] = ""
 | 
						|
	}
 | 
						|
	for k, v := range opt.BuildArgs {
 | 
						|
		so.FrontendAttrs["build-arg:"+k] = v
 | 
						|
	}
 | 
						|
	for k, v := range opt.Labels {
 | 
						|
		so.FrontendAttrs["label:"+k] = v
 | 
						|
	}
 | 
						|
 | 
						|
	for k, v := range node.ProxyConfig {
 | 
						|
		if _, ok := opt.BuildArgs[k]; !ok {
 | 
						|
			so.FrontendAttrs["build-arg:"+k] = v
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// set platforms
 | 
						|
	if len(opt.Platforms) != 0 {
 | 
						|
		pp := make([]string, len(opt.Platforms))
 | 
						|
		for i, p := range opt.Platforms {
 | 
						|
			pp[i] = platforms.Format(p)
 | 
						|
		}
 | 
						|
		if len(pp) > 1 && !nodeDriver.Features(ctx)[driver.MultiPlatform] {
 | 
						|
			return nil, nil, notSupported(driver.MultiPlatform, nodeDriver, "https://docs.docker.com/go/build-multi-platform/")
 | 
						|
		}
 | 
						|
		so.FrontendAttrs["platform"] = strings.Join(pp, ",")
 | 
						|
	}
 | 
						|
 | 
						|
	// setup networkmode
 | 
						|
	switch opt.NetworkMode {
 | 
						|
	case "host":
 | 
						|
		so.FrontendAttrs["force-network-mode"] = opt.NetworkMode
 | 
						|
		so.AllowedEntitlements = append(so.AllowedEntitlements, entitlements.EntitlementNetworkHost)
 | 
						|
	case "none":
 | 
						|
		so.FrontendAttrs["force-network-mode"] = opt.NetworkMode
 | 
						|
	case "", "default":
 | 
						|
	default:
 | 
						|
		return nil, nil, errors.Errorf("network mode %q not supported by buildkit - you can define a custom network for your builder using the network driver-opt in buildx create", opt.NetworkMode)
 | 
						|
	}
 | 
						|
 | 
						|
	// setup extrahosts
 | 
						|
	extraHosts, err := toBuildkitExtraHosts(ctx, opt.ExtraHosts, nodeDriver)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, err
 | 
						|
	}
 | 
						|
	if len(extraHosts) > 0 {
 | 
						|
		so.FrontendAttrs["add-hosts"] = extraHosts
 | 
						|
	}
 | 
						|
 | 
						|
	// setup shm size
 | 
						|
	if opt.ShmSize.Value() > 0 {
 | 
						|
		so.FrontendAttrs["shm-size"] = strconv.FormatInt(opt.ShmSize.Value(), 10)
 | 
						|
	}
 | 
						|
 | 
						|
	// setup ulimits
 | 
						|
	ulimits, err := toBuildkitUlimits(opt.Ulimits)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, err
 | 
						|
	} else if len(ulimits) > 0 {
 | 
						|
		so.FrontendAttrs["ulimit"] = ulimits
 | 
						|
	}
 | 
						|
 | 
						|
	// mark call request as internal
 | 
						|
	if opt.CallFunc != nil {
 | 
						|
		so.Internal = true
 | 
						|
	}
 | 
						|
 | 
						|
	return &so, releaseF, nil
 | 
						|
}
 | 
						|
 | 
						|
func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw progress.Writer, target *client.SolveOpt) (func(), error) {
 | 
						|
	if inp.ContextPath == "" {
 | 
						|
		return nil, errors.New("please specify build context (e.g. \".\" for the current directory)")
 | 
						|
	}
 | 
						|
 | 
						|
	// TODO: handle stdin, symlinks, remote contexts, check files exist
 | 
						|
 | 
						|
	var (
 | 
						|
		err               error
 | 
						|
		dockerfileReader  io.ReadCloser
 | 
						|
		dockerfileDir     string
 | 
						|
		dockerfileName    = inp.DockerfilePath
 | 
						|
		dockerfileSrcName = inp.DockerfilePath
 | 
						|
		toRemove          []string
 | 
						|
	)
 | 
						|
 | 
						|
	switch {
 | 
						|
	case inp.ContextState != nil:
 | 
						|
		if target.FrontendInputs == nil {
 | 
						|
			target.FrontendInputs = make(map[string]llb.State)
 | 
						|
		}
 | 
						|
		target.FrontendInputs["context"] = *inp.ContextState
 | 
						|
		target.FrontendInputs["dockerfile"] = *inp.ContextState
 | 
						|
	case inp.ContextPath == "-":
 | 
						|
		if inp.DockerfilePath == "-" {
 | 
						|
			return nil, errors.Errorf("invalid argument: can't use stdin for both build context and dockerfile")
 | 
						|
		}
 | 
						|
 | 
						|
		rc := inp.InStream.NewReadCloser()
 | 
						|
		magic, err := inp.InStream.Peek(archiveHeaderSize * 2)
 | 
						|
		if err != nil && err != io.EOF {
 | 
						|
			return nil, errors.Wrap(err, "failed to peek context header from STDIN")
 | 
						|
		}
 | 
						|
		if !(err == io.EOF && len(magic) == 0) {
 | 
						|
			if isArchive(magic) {
 | 
						|
				// stdin is context
 | 
						|
				up := uploadprovider.New()
 | 
						|
				target.FrontendAttrs["context"] = up.Add(rc)
 | 
						|
				target.Session = append(target.Session, up)
 | 
						|
			} else {
 | 
						|
				if inp.DockerfilePath != "" {
 | 
						|
					return nil, errors.Errorf("ambiguous Dockerfile source: both stdin and flag correspond to Dockerfiles")
 | 
						|
				}
 | 
						|
				// stdin is dockerfile
 | 
						|
				dockerfileReader = rc
 | 
						|
				inp.ContextPath, _ = os.MkdirTemp("", "empty-dir")
 | 
						|
				toRemove = append(toRemove, inp.ContextPath)
 | 
						|
				if err := setLocalMount("context", inp.ContextPath, target); err != nil {
 | 
						|
					return nil, err
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	case osutil.IsLocalDir(inp.ContextPath):
 | 
						|
		if err := setLocalMount("context", inp.ContextPath, target); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		sharedKey := inp.ContextPath
 | 
						|
		if p, err := filepath.Abs(sharedKey); err == nil {
 | 
						|
			sharedKey = filepath.Base(p)
 | 
						|
		}
 | 
						|
		target.SharedKey = sharedKey
 | 
						|
		switch inp.DockerfilePath {
 | 
						|
		case "-":
 | 
						|
			dockerfileReader = inp.InStream.NewReadCloser()
 | 
						|
		case "":
 | 
						|
			dockerfileDir = inp.ContextPath
 | 
						|
		default:
 | 
						|
			dockerfileDir = filepath.Dir(inp.DockerfilePath)
 | 
						|
			dockerfileName = filepath.Base(inp.DockerfilePath)
 | 
						|
		}
 | 
						|
	case IsRemoteURL(inp.ContextPath):
 | 
						|
		if inp.DockerfilePath == "-" {
 | 
						|
			dockerfileReader = inp.InStream.NewReadCloser()
 | 
						|
		} else if filepath.IsAbs(inp.DockerfilePath) {
 | 
						|
			dockerfileDir = filepath.Dir(inp.DockerfilePath)
 | 
						|
			dockerfileName = filepath.Base(inp.DockerfilePath)
 | 
						|
			target.FrontendAttrs["dockerfilekey"] = "dockerfile"
 | 
						|
		}
 | 
						|
		target.FrontendAttrs["context"] = inp.ContextPath
 | 
						|
	default:
 | 
						|
		return nil, errors.Errorf("unable to prepare context: path %q not found", inp.ContextPath)
 | 
						|
	}
 | 
						|
 | 
						|
	if inp.DockerfileInline != "" {
 | 
						|
		dockerfileReader = io.NopCloser(strings.NewReader(inp.DockerfileInline))
 | 
						|
		dockerfileSrcName = "inline"
 | 
						|
	} else if inp.DockerfilePath == "-" {
 | 
						|
		dockerfileSrcName = "stdin"
 | 
						|
	} else if inp.DockerfilePath == "" {
 | 
						|
		dockerfileSrcName = filepath.Join(inp.ContextPath, "Dockerfile")
 | 
						|
	}
 | 
						|
 | 
						|
	if dockerfileReader != nil {
 | 
						|
		dockerfileDir, err = createTempDockerfile(dockerfileReader, inp.InStream)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		toRemove = append(toRemove, dockerfileDir)
 | 
						|
		dockerfileName = "Dockerfile"
 | 
						|
		target.FrontendAttrs["dockerfilekey"] = "dockerfile"
 | 
						|
	}
 | 
						|
	if isHTTPURL(inp.DockerfilePath) {
 | 
						|
		dockerfileDir, err = createTempDockerfileFromURL(ctx, d, inp.DockerfilePath, pw)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		toRemove = append(toRemove, dockerfileDir)
 | 
						|
		dockerfileName = "Dockerfile"
 | 
						|
		target.FrontendAttrs["dockerfilekey"] = "dockerfile"
 | 
						|
		delete(target.FrontendInputs, "dockerfile")
 | 
						|
	}
 | 
						|
 | 
						|
	if dockerfileName == "" {
 | 
						|
		dockerfileName = "Dockerfile"
 | 
						|
	}
 | 
						|
 | 
						|
	if dockerfileDir != "" {
 | 
						|
		if err := setLocalMount("dockerfile", dockerfileDir, target); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		dockerfileName = handleLowercaseDockerfile(dockerfileDir, dockerfileName)
 | 
						|
	}
 | 
						|
 | 
						|
	target.FrontendAttrs["filename"] = dockerfileName
 | 
						|
 | 
						|
	for k, v := range inp.NamedContexts {
 | 
						|
		target.FrontendAttrs["frontend.caps"] = "moby.buildkit.frontend.contexts+forward"
 | 
						|
		if v.State != nil {
 | 
						|
			target.FrontendAttrs["context:"+k] = "input:" + k
 | 
						|
			if target.FrontendInputs == nil {
 | 
						|
				target.FrontendInputs = make(map[string]llb.State)
 | 
						|
			}
 | 
						|
			target.FrontendInputs[k] = *v.State
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		if IsRemoteURL(v.Path) || strings.HasPrefix(v.Path, "docker-image://") || strings.HasPrefix(v.Path, "target:") {
 | 
						|
			target.FrontendAttrs["context:"+k] = v.Path
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// handle OCI layout
 | 
						|
		if strings.HasPrefix(v.Path, "oci-layout://") {
 | 
						|
			localPath := strings.TrimPrefix(v.Path, "oci-layout://")
 | 
						|
			localPath, dig, hasDigest := strings.Cut(localPath, "@")
 | 
						|
			localPath, tag, hasTag := strings.Cut(localPath, ":")
 | 
						|
			if !hasTag {
 | 
						|
				tag = "latest"
 | 
						|
			}
 | 
						|
			if !hasDigest {
 | 
						|
				dig, err = resolveDigest(localPath, tag)
 | 
						|
				if err != nil {
 | 
						|
					return nil, errors.Wrapf(err, "oci-layout reference %q could not be resolved", v.Path)
 | 
						|
				}
 | 
						|
			}
 | 
						|
			store, err := local.NewStore(localPath)
 | 
						|
			if err != nil {
 | 
						|
				return nil, errors.Wrapf(err, "invalid store at %s", localPath)
 | 
						|
			}
 | 
						|
			storeName := identity.NewID()
 | 
						|
			if target.OCIStores == nil {
 | 
						|
				target.OCIStores = map[string]content.Store{}
 | 
						|
			}
 | 
						|
			target.OCIStores[storeName] = store
 | 
						|
 | 
						|
			target.FrontendAttrs["context:"+k] = "oci-layout://" + storeName + ":" + tag + "@" + dig
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		st, err := os.Stat(v.Path)
 | 
						|
		if err != nil {
 | 
						|
			return nil, errors.Wrapf(err, "failed to get build context %v", k)
 | 
						|
		}
 | 
						|
		if !st.IsDir() {
 | 
						|
			return nil, errors.Wrapf(syscall.ENOTDIR, "failed to get build context path %v", v)
 | 
						|
		}
 | 
						|
		localName := k
 | 
						|
		if k == "context" || k == "dockerfile" {
 | 
						|
			localName = "_" + k // underscore to avoid collisions
 | 
						|
		}
 | 
						|
		if err := setLocalMount(localName, v.Path, target); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		target.FrontendAttrs["context:"+k] = "local:" + localName
 | 
						|
	}
 | 
						|
 | 
						|
	release := func() {
 | 
						|
		for _, dir := range toRemove {
 | 
						|
			_ = os.RemoveAll(dir)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	inp.DockerfileMappingSrc = dockerfileSrcName
 | 
						|
	inp.DockerfileMappingDst = dockerfileName
 | 
						|
	return release, nil
 | 
						|
}
 | 
						|
 | 
						|
func resolveDigest(localPath, tag string) (dig string, _ error) {
 | 
						|
	idx := ociindex.NewStoreIndex(localPath)
 | 
						|
 | 
						|
	// lookup by name
 | 
						|
	desc, err := idx.Get(tag)
 | 
						|
	if err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
	if desc == nil {
 | 
						|
		// lookup single
 | 
						|
		desc, err = idx.GetSingle()
 | 
						|
		if err != nil {
 | 
						|
			return "", err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if desc == nil {
 | 
						|
		return "", errors.New("failed to resolve digest")
 | 
						|
	}
 | 
						|
 | 
						|
	dig = string(desc.Digest)
 | 
						|
	_, err = digest.Parse(dig)
 | 
						|
	if err != nil {
 | 
						|
		return "", errors.Wrapf(err, "invalid digest %s", dig)
 | 
						|
	}
 | 
						|
 | 
						|
	return dig, nil
 | 
						|
}
 | 
						|
 | 
						|
func setLocalMount(name, dir string, so *client.SolveOpt) error {
 | 
						|
	lm, err := fsutil.NewFS(dir)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if so.LocalMounts == nil {
 | 
						|
		so.LocalMounts = map[string]fsutil.FS{}
 | 
						|
	}
 | 
						|
	so.LocalMounts[name] = &fs{FS: lm, dir: dir}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func createTempDockerfile(r io.Reader, multiReader *SyncMultiReader) (string, error) {
 | 
						|
	dir, err := os.MkdirTemp("", "dockerfile")
 | 
						|
	if err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
	f, err := os.Create(filepath.Join(dir, "Dockerfile"))
 | 
						|
	if err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
	defer f.Close()
 | 
						|
 | 
						|
	if multiReader != nil {
 | 
						|
		dt, err := io.ReadAll(r)
 | 
						|
		if err != nil {
 | 
						|
			return "", err
 | 
						|
		}
 | 
						|
		multiReader.Reset(dt)
 | 
						|
		r = bytes.NewReader(dt)
 | 
						|
	}
 | 
						|
 | 
						|
	if _, err := io.Copy(f, r); err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
	return dir, err
 | 
						|
}
 | 
						|
 | 
						|
// handle https://github.com/moby/moby/pull/10858
 | 
						|
func handleLowercaseDockerfile(dir, p string) string {
 | 
						|
	if filepath.Base(p) != "Dockerfile" {
 | 
						|
		return p
 | 
						|
	}
 | 
						|
 | 
						|
	f, err := os.Open(filepath.Dir(filepath.Join(dir, p)))
 | 
						|
	if err != nil {
 | 
						|
		return p
 | 
						|
	}
 | 
						|
 | 
						|
	names, err := f.Readdirnames(-1)
 | 
						|
	if err != nil {
 | 
						|
		return p
 | 
						|
	}
 | 
						|
 | 
						|
	foundLowerCase := false
 | 
						|
	for _, n := range names {
 | 
						|
		if n == "Dockerfile" {
 | 
						|
			return p
 | 
						|
		}
 | 
						|
		if n == "dockerfile" {
 | 
						|
			foundLowerCase = true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if foundLowerCase {
 | 
						|
		return filepath.Join(filepath.Dir(p), "dockerfile")
 | 
						|
	}
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
type fs struct {
 | 
						|
	fsutil.FS
 | 
						|
	dir string
 | 
						|
}
 | 
						|
 | 
						|
var _ fsutil.FS = &fs{}
 |