mirror of
https://github.com/docker/buildx.git
synced 2024-11-22 15:37:16 +08:00
build: move funcs related to solve opts handling
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
parent
4327ee73b1
commit
4b516af1f6
605
build/build.go
605
build/build.go
@ -1,7 +1,6 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
_ "crypto/sha256" // ensure digests can be computed
|
||||
@ -10,44 +9,32 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/content/local"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/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/desktop"
|
||||
"github.com/docker/buildx/util/dockerutil"
|
||||
"github.com/docker/buildx/util/imagetools"
|
||||
"github.com/docker/buildx/util/osutil"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/buildx/util/resolver"
|
||||
"github.com/docker/buildx/util/waitmap"
|
||||
"github.com/docker/cli/opts"
|
||||
imagetypes "github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/builder/remotecontext/urlutil"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/client/ociindex"
|
||||
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
||||
gateway "github.com/moby/buildkit/frontend/gateway/client"
|
||||
"github.com/moby/buildkit/identity"
|
||||
"github.com/moby/buildkit/session"
|
||||
"github.com/moby/buildkit/session/upload/uploadprovider"
|
||||
"github.com/moby/buildkit/solver/errdefs"
|
||||
"github.com/moby/buildkit/solver/pb"
|
||||
spb "github.com/moby/buildkit/sourcepolicy/pb"
|
||||
"github.com/moby/buildkit/util/apicaps"
|
||||
"github.com/moby/buildkit/util/entitlements"
|
||||
"github.com/moby/buildkit/util/progress/progresswriter"
|
||||
"github.com/moby/buildkit/util/tracing"
|
||||
@ -157,324 +144,6 @@ func toRepoOnly(in string) (string, error) {
|
||||
return strings.Join(out, ","), nil
|
||||
}
|
||||
|
||||
func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt Options, bopts gateway.BuildOpts, configDir string, pw progress.Writer, docker *dockerutil.Client) (solveOpt *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{},
|
||||
LocalDirs: map[string]string{},
|
||||
CacheExports: cacheTo,
|
||||
CacheImports: cacheFrom,
|
||||
AllowedEntitlements: opt.Allow,
|
||||
SourcePolicy: opt.SourcePolicy,
|
||||
}
|
||||
|
||||
if so.Ref == "" {
|
||||
so.Ref = identity.NewID()
|
||||
}
|
||||
|
||||
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 nodeDriver.IsMobyDriver() && !noDefaultLoad() {
|
||||
// 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{}}}
|
||||
}
|
||||
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 {
|
||||
e.Attrs["buildinfo-attrs"] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
so.Exports = opt.Exports
|
||||
so.Session = opt.Session
|
||||
|
||||
releaseLoad, err := LoadInputs(ctx, nodeDriver, opt.Inputs, pw, &so)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defers = append(defers, releaseLoad)
|
||||
|
||||
if sharedKey := so.LocalDirs["context"]; sharedKey != "" {
|
||||
if p, err := filepath.Abs(sharedKey); err == nil {
|
||||
sharedKey = filepath.Base(p)
|
||||
}
|
||||
so.SharedKey = sharedKey + ":" + confutil.TryNodeIdentifier(configDir)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return &so, releaseF, nil
|
||||
}
|
||||
|
||||
func Build(ctx context.Context, nodes []builder.Node, opt map[string]Options, docker *dockerutil.Client, configDir string, w progress.Writer) (resp map[string]*client.SolveResponse, err error) {
|
||||
return BuildWithResultHandler(ctx, nodes, opt, docker, configDir, w, nil)
|
||||
}
|
||||
@ -1095,231 +764,6 @@ func remoteDigestWithMoby(ctx context.Context, d driver.Driver, name string) (st
|
||||
return remoteImage.Descriptor.Digest.String(), nil
|
||||
}
|
||||
|
||||
func createTempDockerfile(r io.Reader) (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 _, err := io.Copy(f, r); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dir, err
|
||||
}
|
||||
|
||||
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.Reader
|
||||
dockerfileDir string
|
||||
dockerfileName = 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, errStdinConflict
|
||||
}
|
||||
|
||||
buf := bufio.NewReader(inp.InStream)
|
||||
magic, err := buf.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(buf)
|
||||
target.Session = append(target.Session, up)
|
||||
} else {
|
||||
if inp.DockerfilePath != "" {
|
||||
return nil, errDockerfileConflict
|
||||
}
|
||||
// stdin is dockerfile
|
||||
dockerfileReader = buf
|
||||
inp.ContextPath, _ = os.MkdirTemp("", "empty-dir")
|
||||
toRemove = append(toRemove, inp.ContextPath)
|
||||
target.LocalDirs["context"] = inp.ContextPath
|
||||
}
|
||||
}
|
||||
case osutil.IsLocalDir(inp.ContextPath):
|
||||
target.LocalDirs["context"] = inp.ContextPath
|
||||
switch inp.DockerfilePath {
|
||||
case "-":
|
||||
dockerfileReader = inp.InStream
|
||||
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
|
||||
} 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 = strings.NewReader(inp.DockerfileInline)
|
||||
}
|
||||
|
||||
if dockerfileReader != nil {
|
||||
dockerfileDir, err = createTempDockerfile(dockerfileReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toRemove = append(toRemove, dockerfileDir)
|
||||
dockerfileName = "Dockerfile"
|
||||
target.FrontendAttrs["dockerfilekey"] = "dockerfile"
|
||||
}
|
||||
if urlutil.IsURL(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 != "" {
|
||||
target.LocalDirs["dockerfile"] = dockerfileDir
|
||||
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://") {
|
||||
pathAlone := strings.TrimPrefix(v.Path, "oci-layout://")
|
||||
localPath := pathAlone
|
||||
localPath, dig, hasDigest := strings.Cut(localPath, "@")
|
||||
localPath, tag, hasTag := strings.Cut(localPath, ":")
|
||||
if !hasTag {
|
||||
tag = "latest"
|
||||
hasTag = true
|
||||
}
|
||||
idx := ociindex.NewStoreIndex(localPath)
|
||||
if !hasDigest {
|
||||
// lookup by name
|
||||
desc, err := idx.Get(tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if desc != nil {
|
||||
dig = string(desc.Digest)
|
||||
hasDigest = true
|
||||
}
|
||||
}
|
||||
if !hasDigest {
|
||||
// lookup single
|
||||
desc, err := idx.GetSingle()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if desc != nil {
|
||||
dig = string(desc.Digest)
|
||||
hasDigest = true
|
||||
}
|
||||
}
|
||||
if !hasDigest {
|
||||
return nil, errors.Errorf("oci-layout reference %q could not be resolved", v.Path)
|
||||
}
|
||||
_, err := digest.Parse(dig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid oci-layout digest %s", dig)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
layout := "oci-layout://" + storeName
|
||||
if hasTag {
|
||||
layout += ":" + tag
|
||||
}
|
||||
if hasDigest {
|
||||
layout += "@" + dig
|
||||
}
|
||||
|
||||
target.FrontendAttrs["context:"+k] = layout
|
||||
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
|
||||
}
|
||||
target.LocalDirs[localName] = v.Path
|
||||
target.FrontendAttrs["context:"+k] = "local:" + localName
|
||||
}
|
||||
|
||||
release := func() {
|
||||
for _, dir := range toRemove {
|
||||
os.RemoveAll(dir)
|
||||
}
|
||||
}
|
||||
return release, nil
|
||||
}
|
||||
|
||||
func resultKey(index int, name string) string {
|
||||
return fmt.Sprintf("%d-%s", index, name)
|
||||
}
|
||||
@ -1428,55 +872,6 @@ func waitContextDeps(ctx context.Context, index int, results *waitmap.Map, so *c
|
||||
return nil
|
||||
}
|
||||
|
||||
func notSupported(f driver.Feature, d driver.Driver, docs string) error {
|
||||
return errors.Errorf(`%s is not supported for the %s driver.
|
||||
Switch to a different driver, or turn on the containerd image store, and try again.
|
||||
Learn more at %s`, f, d.Factory().Name(), docs)
|
||||
}
|
||||
|
||||
func noDefaultLoad() bool {
|
||||
v, ok := os.LookupEnv("BUILDX_NO_DEFAULT_LOAD")
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
b, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
logrus.Warnf("invalid non-bool value for BUILDX_NO_DEFAULT_LOAD: %s", v)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func noPrintFunc(opt map[string]Options) bool {
|
||||
for _, v := range opt {
|
||||
if v.PrintFunc != nil {
|
||||
|
609
build/opt.go
Normal file
609
build/opt.go
Normal file
@ -0,0 +1,609 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/content/local"
|
||||
"github.com/containerd/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/docker/docker/builder/remotecontext/urlutil"
|
||||
"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"
|
||||
)
|
||||
|
||||
func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt Options, bopts gateway.BuildOpts, configDir string, pw progress.Writer, docker *dockerutil.Client) (solveOpt *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{},
|
||||
LocalDirs: map[string]string{},
|
||||
CacheExports: cacheTo,
|
||||
CacheImports: cacheFrom,
|
||||
AllowedEntitlements: opt.Allow,
|
||||
SourcePolicy: opt.SourcePolicy,
|
||||
}
|
||||
|
||||
if so.Ref == "" {
|
||||
so.Ref = identity.NewID()
|
||||
}
|
||||
|
||||
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 nodeDriver.IsMobyDriver() && !noDefaultLoad() {
|
||||
// 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{}}}
|
||||
}
|
||||
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 {
|
||||
e.Attrs["buildinfo-attrs"] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
so.Exports = opt.Exports
|
||||
so.Session = opt.Session
|
||||
|
||||
releaseLoad, err := LoadInputs(ctx, nodeDriver, opt.Inputs, pw, &so)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defers = append(defers, releaseLoad)
|
||||
|
||||
if sharedKey := so.LocalDirs["context"]; sharedKey != "" {
|
||||
if p, err := filepath.Abs(sharedKey); err == nil {
|
||||
sharedKey = filepath.Base(p)
|
||||
}
|
||||
so.SharedKey = sharedKey + ":" + confutil.TryNodeIdentifier(configDir)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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.Reader
|
||||
dockerfileDir string
|
||||
dockerfileName = 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, errStdinConflict
|
||||
}
|
||||
|
||||
buf := bufio.NewReader(inp.InStream)
|
||||
magic, err := buf.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(buf)
|
||||
target.Session = append(target.Session, up)
|
||||
} else {
|
||||
if inp.DockerfilePath != "" {
|
||||
return nil, errDockerfileConflict
|
||||
}
|
||||
// stdin is dockerfile
|
||||
dockerfileReader = buf
|
||||
inp.ContextPath, _ = os.MkdirTemp("", "empty-dir")
|
||||
toRemove = append(toRemove, inp.ContextPath)
|
||||
target.LocalDirs["context"] = inp.ContextPath
|
||||
}
|
||||
}
|
||||
case osutil.IsLocalDir(inp.ContextPath):
|
||||
target.LocalDirs["context"] = inp.ContextPath
|
||||
switch inp.DockerfilePath {
|
||||
case "-":
|
||||
dockerfileReader = inp.InStream
|
||||
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
|
||||
} 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 = strings.NewReader(inp.DockerfileInline)
|
||||
}
|
||||
|
||||
if dockerfileReader != nil {
|
||||
dockerfileDir, err = createTempDockerfile(dockerfileReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toRemove = append(toRemove, dockerfileDir)
|
||||
dockerfileName = "Dockerfile"
|
||||
target.FrontendAttrs["dockerfilekey"] = "dockerfile"
|
||||
}
|
||||
if urlutil.IsURL(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 != "" {
|
||||
target.LocalDirs["dockerfile"] = dockerfileDir
|
||||
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://") {
|
||||
pathAlone := strings.TrimPrefix(v.Path, "oci-layout://")
|
||||
localPath := pathAlone
|
||||
localPath, dig, hasDigest := strings.Cut(localPath, "@")
|
||||
localPath, tag, hasTag := strings.Cut(localPath, ":")
|
||||
if !hasTag {
|
||||
tag = "latest"
|
||||
hasTag = true
|
||||
}
|
||||
idx := ociindex.NewStoreIndex(localPath)
|
||||
if !hasDigest {
|
||||
// lookup by name
|
||||
desc, err := idx.Get(tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if desc != nil {
|
||||
dig = string(desc.Digest)
|
||||
hasDigest = true
|
||||
}
|
||||
}
|
||||
if !hasDigest {
|
||||
// lookup single
|
||||
desc, err := idx.GetSingle()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if desc != nil {
|
||||
dig = string(desc.Digest)
|
||||
hasDigest = true
|
||||
}
|
||||
}
|
||||
if !hasDigest {
|
||||
return nil, errors.Errorf("oci-layout reference %q could not be resolved", v.Path)
|
||||
}
|
||||
_, err := digest.Parse(dig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid oci-layout digest %s", dig)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
layout := "oci-layout://" + storeName
|
||||
if hasTag {
|
||||
layout += ":" + tag
|
||||
}
|
||||
if hasDigest {
|
||||
layout += "@" + dig
|
||||
}
|
||||
|
||||
target.FrontendAttrs["context:"+k] = layout
|
||||
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
|
||||
}
|
||||
target.LocalDirs[localName] = v.Path
|
||||
target.FrontendAttrs["context:"+k] = "local:" + localName
|
||||
}
|
||||
|
||||
release := func() {
|
||||
for _, dir := range toRemove {
|
||||
os.RemoveAll(dir)
|
||||
}
|
||||
}
|
||||
return release, nil
|
||||
}
|
||||
|
||||
func createTempDockerfile(r io.Reader) (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 _, 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
|
||||
}
|
@ -5,6 +5,8 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/driver"
|
||||
@ -12,6 +14,7 @@ import (
|
||||
"github.com/docker/docker/builder/remotecontext/urlutil"
|
||||
"github.com/moby/buildkit/util/gitutil"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -101,3 +104,21 @@ func toBuildkitUlimits(inp *opts.UlimitOpt) (string, error) {
|
||||
}
|
||||
return strings.Join(ulimits, ","), nil
|
||||
}
|
||||
|
||||
func notSupported(f driver.Feature, d driver.Driver, docs string) error {
|
||||
return errors.Errorf(`%s is not supported for the %s driver.
|
||||
Switch to a different driver, or turn on the containerd image store, and try again.
|
||||
Learn more at %s`, f, d.Factory().Name(), docs)
|
||||
}
|
||||
|
||||
func noDefaultLoad() bool {
|
||||
v, ok := os.LookupEnv("BUILDX_NO_DEFAULT_LOAD")
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
b, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
logrus.Warnf("invalid non-bool value for BUILDX_NO_DEFAULT_LOAD: %s", v)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user