mirror of https://github.com/docker/buildx.git
bake: update ReadRemoteFiles to use buildkit api
Signed-off-by: Justin Chadwell <me@jedevc.com>
This commit is contained in:
parent
62a21520ea
commit
871f865ac8
|
@ -27,9 +27,6 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
httpPrefix = regexp.MustCompile(`^https?://`)
|
||||
gitURLPathWithFragmentSuffix = regexp.MustCompile(`\.git(?:#.+)?$`)
|
||||
|
||||
validTargetNameChars = `[a-zA-Z0-9_-]+`
|
||||
targetNamePattern = regexp.MustCompile(`^` + validTargetNameChars + `$`)
|
||||
)
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/builder"
|
||||
controllerapi "github.com/docker/buildx/controller/pb"
|
||||
|
@ -12,6 +11,7 @@ import (
|
|||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/frontend/dockerui"
|
||||
gwclient "github.com/moby/buildkit/frontend/gateway/client"
|
||||
"github.com/moby/buildkit/session"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -25,14 +25,14 @@ type Input struct {
|
|||
func ReadRemoteFiles(ctx context.Context, nodes []builder.Node, url string, names []string, pw progress.Writer) ([]File, *Input, error) {
|
||||
var session []session.Attachable
|
||||
var filename string
|
||||
st, ok := detectGitContext(url)
|
||||
st, ok := dockerui.DetectGitContext(url, false)
|
||||
if ok {
|
||||
ssh, err := controllerapi.CreateSSH([]*controllerapi.SSH{{ID: "default"}})
|
||||
if err == nil {
|
||||
session = append(session, ssh)
|
||||
}
|
||||
} else {
|
||||
st, filename, ok = detectHTTPContext(url)
|
||||
st, filename, ok = dockerui.DetectHTTPContext(url)
|
||||
if !ok {
|
||||
return nil, nil, errors.Errorf("not url context")
|
||||
}
|
||||
|
@ -91,41 +91,6 @@ func ReadRemoteFiles(ctx context.Context, nodes []builder.Node, url string, name
|
|||
return files, inp, nil
|
||||
}
|
||||
|
||||
func detectHTTPContext(url string) (*llb.State, string, bool) {
|
||||
if httpPrefix.MatchString(url) {
|
||||
httpContext := llb.HTTP(url, llb.Filename("context"), llb.WithCustomName("[internal] load remote build context"))
|
||||
return &httpContext, "context", true
|
||||
}
|
||||
return nil, "", false
|
||||
}
|
||||
|
||||
func detectGitContext(ref string) (*llb.State, bool) {
|
||||
found := false
|
||||
if httpPrefix.MatchString(ref) && gitURLPathWithFragmentSuffix.MatchString(ref) {
|
||||
found = true
|
||||
}
|
||||
|
||||
for _, prefix := range []string{"git://", "github.com/", "git@"} {
|
||||
if strings.HasPrefix(ref, prefix) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
parts := strings.SplitN(ref, "#", 2)
|
||||
branch := ""
|
||||
if len(parts) > 1 {
|
||||
branch = parts[1]
|
||||
}
|
||||
gitOpts := []llb.GitOption{llb.WithCustomName("[internal] load git source " + ref)}
|
||||
|
||||
st := llb.Git(parts[0], branch, gitOpts...)
|
||||
return &st, true
|
||||
}
|
||||
|
||||
func isArchive(header []byte) bool {
|
||||
for _, m := range [][]byte{
|
||||
{0x42, 0x5A, 0x68}, // bzip2
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
package attestations
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
KeyTypeSbom = "sbom"
|
||||
KeyTypeProvenance = "provenance"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultSBOMGenerator = "docker/buildkit-syft-scanner:stable-1"
|
||||
)
|
||||
|
||||
func Filter(v map[string]string) map[string]string {
|
||||
attests := make(map[string]string)
|
||||
for k, v := range v {
|
||||
if strings.HasPrefix(k, "attest:") {
|
||||
attests[k] = v
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(k, "build-arg:BUILDKIT_ATTEST_") {
|
||||
attests[k] = v
|
||||
continue
|
||||
}
|
||||
}
|
||||
return attests
|
||||
}
|
||||
|
||||
func Validate(values map[string]map[string]string) (map[string]map[string]string, error) {
|
||||
for k := range values {
|
||||
if k != KeyTypeSbom && k != KeyTypeProvenance {
|
||||
return nil, errors.Errorf("unknown attestation type %q", k)
|
||||
}
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func Parse(values map[string]string) (map[string]map[string]string, error) {
|
||||
attests := make(map[string]string)
|
||||
for k, v := range values {
|
||||
if strings.HasPrefix(k, "attest:") {
|
||||
attests[strings.ToLower(strings.TrimPrefix(k, "attest:"))] = v
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(k, "build-arg:BUILDKIT_ATTEST_") {
|
||||
attests[strings.ToLower(strings.TrimPrefix(k, "build-arg:BUILDKIT_ATTEST_"))] = v
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
out := make(map[string]map[string]string)
|
||||
for k, v := range attests {
|
||||
attrs := make(map[string]string)
|
||||
out[k] = attrs
|
||||
if k == KeyTypeSbom {
|
||||
attrs["generator"] = defaultSBOMGenerator
|
||||
}
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
csvReader := csv.NewReader(strings.NewReader(v))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse %s", k)
|
||||
}
|
||||
for _, field := range fields {
|
||||
parts := strings.SplitN(field, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
parts = append(parts, "")
|
||||
}
|
||||
attrs[parts[0]] = parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
return Validate(out)
|
||||
}
|
65
vendor/github.com/moby/buildkit/frontend/dockerfile/dockerignore/dockerignore.go
generated
vendored
Normal file
65
vendor/github.com/moby/buildkit/frontend/dockerfile/dockerignore/dockerignore.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
package dockerignore
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ReadAll reads a .dockerignore file and returns the list of file patterns
|
||||
// to ignore. Note this will trim whitespace from each line as well
|
||||
// as use GO's "clean" func to get the shortest/cleanest path for each.
|
||||
func ReadAll(reader io.Reader) ([]string, error) {
|
||||
if reader == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(reader)
|
||||
var excludes []string
|
||||
currentLine := 0
|
||||
|
||||
utf8bom := []byte{0xEF, 0xBB, 0xBF}
|
||||
for scanner.Scan() {
|
||||
scannedBytes := scanner.Bytes()
|
||||
// We trim UTF8 BOM
|
||||
if currentLine == 0 {
|
||||
scannedBytes = bytes.TrimPrefix(scannedBytes, utf8bom)
|
||||
}
|
||||
pattern := string(scannedBytes)
|
||||
currentLine++
|
||||
// Lines starting with # (comments) are ignored before processing
|
||||
if strings.HasPrefix(pattern, "#") {
|
||||
continue
|
||||
}
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
if pattern == "" {
|
||||
continue
|
||||
}
|
||||
// normalize absolute paths to paths relative to the context
|
||||
// (taking care of '!' prefix)
|
||||
invert := pattern[0] == '!'
|
||||
if invert {
|
||||
pattern = strings.TrimSpace(pattern[1:])
|
||||
}
|
||||
if len(pattern) > 0 {
|
||||
pattern = filepath.Clean(pattern)
|
||||
pattern = filepath.ToSlash(pattern)
|
||||
if len(pattern) > 1 && pattern[0] == '/' {
|
||||
pattern = pattern[1:]
|
||||
}
|
||||
}
|
||||
if invert {
|
||||
pattern = "!" + pattern
|
||||
}
|
||||
|
||||
excludes = append(excludes, pattern)
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, errors.Wrap(err, "error reading .dockerignore")
|
||||
}
|
||||
return excludes, nil
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package dockerui
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/solver/pb"
|
||||
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func parsePlatforms(v string) ([]ocispecs.Platform, error) {
|
||||
var pp []ocispecs.Platform
|
||||
for _, v := range strings.Split(v, ",") {
|
||||
p, err := platforms.Parse(v)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse target platform %s", v)
|
||||
}
|
||||
pp = append(pp, platforms.Normalize(p))
|
||||
}
|
||||
return pp, nil
|
||||
}
|
||||
|
||||
func parseResolveMode(v string) (llb.ResolveMode, error) {
|
||||
switch v {
|
||||
case pb.AttrImageResolveModeDefault, "":
|
||||
return llb.ResolveModeDefault, nil
|
||||
case pb.AttrImageResolveModeForcePull:
|
||||
return llb.ResolveModeForcePull, nil
|
||||
case pb.AttrImageResolveModePreferLocal:
|
||||
return llb.ResolveModePreferLocal, nil
|
||||
default:
|
||||
return 0, errors.Errorf("invalid image-resolve-mode: %s", v)
|
||||
}
|
||||
}
|
||||
|
||||
func parseExtraHosts(v string) ([]llb.HostIP, error) {
|
||||
if v == "" {
|
||||
return nil, nil
|
||||
}
|
||||
out := make([]llb.HostIP, 0)
|
||||
csvReader := csv.NewReader(strings.NewReader(v))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, field := range fields {
|
||||
key, val, ok := strings.Cut(strings.ToLower(field), "=")
|
||||
if !ok {
|
||||
return nil, errors.Errorf("invalid key-value pair %s", field)
|
||||
}
|
||||
ip := net.ParseIP(val)
|
||||
if ip == nil {
|
||||
return nil, errors.Errorf("failed to parse IP %s", val)
|
||||
}
|
||||
out = append(out, llb.HostIP{Host: key, IP: ip})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func parseShmSize(v string) (int64, error) {
|
||||
if len(v) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
kb, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return kb, nil
|
||||
}
|
||||
|
||||
func parseUlimits(v string) ([]pb.Ulimit, error) {
|
||||
if v == "" {
|
||||
return nil, nil
|
||||
}
|
||||
out := make([]pb.Ulimit, 0)
|
||||
csvReader := csv.NewReader(strings.NewReader(v))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, field := range fields {
|
||||
ulimit, err := units.ParseUlimit(field)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, pb.Ulimit{
|
||||
Name: ulimit.Name,
|
||||
Soft: ulimit.Soft,
|
||||
Hard: ulimit.Hard,
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func parseNetMode(v string) (pb.NetMode, error) {
|
||||
if v == "" {
|
||||
return llb.NetModeSandbox, nil
|
||||
}
|
||||
switch v {
|
||||
case "none":
|
||||
return llb.NetModeNone, nil
|
||||
case "host":
|
||||
return llb.NetModeHost, nil
|
||||
case "sandbox":
|
||||
return llb.NetModeSandbox, nil
|
||||
default:
|
||||
return 0, errors.Errorf("invalid netmode %s", v)
|
||||
}
|
||||
}
|
||||
|
||||
func parseSourceDateEpoch(v string) (*time.Time, error) {
|
||||
if v == "" {
|
||||
return nil, nil
|
||||
}
|
||||
sde, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid SOURCE_DATE_EPOCH: %s", v)
|
||||
}
|
||||
tm := time.Unix(sde, 0).UTC()
|
||||
return &tm, nil
|
||||
}
|
||||
|
||||
func filter(opt map[string]string, key string) map[string]string {
|
||||
m := map[string]string{}
|
||||
for k, v := range opt {
|
||||
if strings.HasPrefix(k, key) {
|
||||
m[strings.TrimPrefix(k, key)] = v
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
package dockerui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
||||
"github.com/moby/buildkit/exporter/containerimage/image"
|
||||
"github.com/moby/buildkit/frontend/gateway/client"
|
||||
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type BuildFunc func(ctx context.Context, platform *ocispecs.Platform, idx int) (client.Reference, *image.Image, error)
|
||||
|
||||
func (bc *Client) Build(ctx context.Context, fn BuildFunc) (*ResultBuilder, error) {
|
||||
res := client.NewResult()
|
||||
|
||||
targets := make([]*ocispecs.Platform, 0, len(bc.TargetPlatforms))
|
||||
for _, p := range bc.TargetPlatforms {
|
||||
p := p
|
||||
targets = append(targets, &p)
|
||||
}
|
||||
if len(targets) == 0 {
|
||||
targets = append(targets, nil)
|
||||
}
|
||||
expPlatforms := &exptypes.Platforms{
|
||||
Platforms: make([]exptypes.Platform, len(targets)),
|
||||
}
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
for i, tp := range targets {
|
||||
i, tp := i, tp
|
||||
eg.Go(func() error {
|
||||
ref, img, err := fn(ctx, tp, i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, err := json.Marshal(img)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to marshal image config")
|
||||
}
|
||||
|
||||
p := platforms.DefaultSpec()
|
||||
if tp != nil {
|
||||
p = *tp
|
||||
}
|
||||
|
||||
// in certain conditions we allow input platform to be extended from base image
|
||||
if p.OS == "windows" && img.OS == p.OS {
|
||||
if p.OSVersion == "" && img.OSVersion != "" {
|
||||
p.OSVersion = img.OSVersion
|
||||
}
|
||||
if p.OSFeatures == nil && len(img.OSFeatures) > 0 {
|
||||
p.OSFeatures = img.OSFeatures
|
||||
}
|
||||
}
|
||||
|
||||
p = platforms.Normalize(p)
|
||||
k := platforms.Format(p)
|
||||
|
||||
if bc.MultiPlatformRequested {
|
||||
res.AddRef(k, ref)
|
||||
res.AddMeta(fmt.Sprintf("%s/%s", exptypes.ExporterImageConfigKey, k), config)
|
||||
} else {
|
||||
res.SetRef(ref)
|
||||
res.AddMeta(exptypes.ExporterImageConfigKey, config)
|
||||
}
|
||||
expPlatforms.Platforms[i] = exptypes.Platform{
|
||||
ID: k,
|
||||
Platform: p,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := eg.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ResultBuilder{
|
||||
Result: res,
|
||||
expPlatforms: expPlatforms,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type ResultBuilder struct {
|
||||
*client.Result
|
||||
expPlatforms *exptypes.Platforms
|
||||
}
|
||||
|
||||
func (rb *ResultBuilder) Finalize() (*client.Result, error) {
|
||||
dt, err := json.Marshal(rb.expPlatforms)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rb.AddMeta(exptypes.ExporterPlatformsKey, dt)
|
||||
|
||||
return rb.Result, nil
|
||||
}
|
||||
|
||||
func (rb *ResultBuilder) EachPlatform(ctx context.Context, fn func(ctx context.Context, id string, p ocispecs.Platform) error) error {
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
for _, p := range rb.expPlatforms.Platforms {
|
||||
p := p
|
||||
eg.Go(func() error {
|
||||
return fn(ctx, p.ID, p.Platform)
|
||||
})
|
||||
}
|
||||
return eg.Wait()
|
||||
}
|
|
@ -0,0 +1,497 @@
|
|||
package dockerui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/docker/distribution/reference"
|
||||
controlapi "github.com/moby/buildkit/api/services/control"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/exporter/containerimage/image"
|
||||
"github.com/moby/buildkit/frontend/attestations"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/dockerignore"
|
||||
"github.com/moby/buildkit/frontend/gateway/client"
|
||||
"github.com/moby/buildkit/solver/pb"
|
||||
"github.com/moby/buildkit/util/flightcontrol"
|
||||
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
buildArgPrefix = "build-arg:"
|
||||
labelPrefix = "label:"
|
||||
|
||||
keyTarget = "target"
|
||||
keyCgroupParent = "cgroup-parent"
|
||||
keyForceNetwork = "force-network-mode"
|
||||
keyGlobalAddHosts = "add-hosts"
|
||||
keyHostname = "hostname"
|
||||
keyImageResolveMode = "image-resolve-mode"
|
||||
keyMultiPlatform = "multi-platform"
|
||||
keyNoCache = "no-cache"
|
||||
keyShmSize = "shm-size"
|
||||
keyTargetPlatform = "platform"
|
||||
keyUlimit = "ulimit"
|
||||
keyCacheFrom = "cache-from" // for registry only. deprecated in favor of keyCacheImports
|
||||
keyCacheImports = "cache-imports" // JSON representation of []CacheOptionsEntry
|
||||
|
||||
// Don't forget to update frontend documentation if you add
|
||||
// a new build-arg: frontend/dockerfile/docs/reference.md
|
||||
keyCacheNSArg = "build-arg:BUILDKIT_CACHE_MOUNT_NS"
|
||||
keyMultiPlatformArg = "build-arg:BUILDKIT_MULTI_PLATFORM"
|
||||
keyHostnameArg = "build-arg:BUILDKIT_SANDBOX_HOSTNAME"
|
||||
keyContextKeepGitDirArg = "build-arg:BUILDKIT_CONTEXT_KEEP_GIT_DIR"
|
||||
keySourceDateEpoch = "build-arg:SOURCE_DATE_EPOCH"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
BuildArgs map[string]string
|
||||
CacheIDNamespace string
|
||||
CgroupParent string
|
||||
Epoch *time.Time
|
||||
ExtraHosts []llb.HostIP
|
||||
Hostname string
|
||||
ImageResolveMode llb.ResolveMode
|
||||
Labels map[string]string
|
||||
NetworkMode pb.NetMode
|
||||
ShmSize int64
|
||||
Target string
|
||||
Ulimits []pb.Ulimit
|
||||
|
||||
CacheImports []client.CacheOptionsEntry
|
||||
TargetPlatforms []ocispecs.Platform // nil means default
|
||||
BuildPlatforms []ocispecs.Platform
|
||||
MultiPlatformRequested bool
|
||||
SBOM *SBOM
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
Config
|
||||
client client.Client
|
||||
ignoreCache []string
|
||||
bctx *buildContext
|
||||
g flightcontrol.Group
|
||||
bopts client.BuildOpts
|
||||
|
||||
dockerignore []byte
|
||||
}
|
||||
|
||||
type SBOM struct {
|
||||
Generator string
|
||||
}
|
||||
|
||||
type Source struct {
|
||||
*llb.SourceMap
|
||||
Warn func(context.Context, string, client.WarnOpts)
|
||||
}
|
||||
|
||||
type ContextOpt struct {
|
||||
NoDockerignore bool
|
||||
LocalOpts []llb.LocalOption
|
||||
Platform *ocispecs.Platform
|
||||
ResolveMode string
|
||||
}
|
||||
|
||||
func validateMinCaps(c client.Client) error {
|
||||
opts := c.BuildOpts().Opts
|
||||
caps := c.BuildOpts().LLBCaps
|
||||
|
||||
if err := caps.Supports(pb.CapFileBase); err != nil {
|
||||
return errors.Wrap(err, "needs BuildKit 0.5 or later")
|
||||
}
|
||||
if opts["override-copy-image"] != "" {
|
||||
return errors.New("support for \"override-copy-image\" was removed in BuildKit 0.11")
|
||||
}
|
||||
if v, ok := opts["build-arg:BUILDKIT_DISABLE_FILEOP"]; ok {
|
||||
if b, err := strconv.ParseBool(v); err == nil && b {
|
||||
return errors.New("support for \"BUILDKIT_DISABLE_FILEOP\" build-arg was removed in BuildKit 0.11")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewClient(c client.Client) (*Client, error) {
|
||||
if err := validateMinCaps(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bc := &Client{
|
||||
client: c,
|
||||
bopts: c.BuildOpts(), // avoid grpc on every call
|
||||
}
|
||||
|
||||
if err := bc.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bc, nil
|
||||
}
|
||||
|
||||
func (bc *Client) BuildOpts() client.BuildOpts {
|
||||
return bc.bopts
|
||||
}
|
||||
|
||||
func (bc *Client) init() error {
|
||||
opts := bc.bopts.Opts
|
||||
|
||||
defaultBuildPlatform := platforms.Normalize(platforms.DefaultSpec())
|
||||
if workers := bc.bopts.Workers; len(workers) > 0 && len(workers[0].Platforms) > 0 {
|
||||
defaultBuildPlatform = workers[0].Platforms[0]
|
||||
}
|
||||
buildPlatforms := []ocispecs.Platform{defaultBuildPlatform}
|
||||
targetPlatforms := []ocispecs.Platform{}
|
||||
if v := opts[keyTargetPlatform]; v != "" {
|
||||
var err error
|
||||
targetPlatforms, err = parsePlatforms(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
bc.BuildPlatforms = buildPlatforms
|
||||
bc.TargetPlatforms = targetPlatforms
|
||||
|
||||
resolveMode, err := parseResolveMode(opts[keyImageResolveMode])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bc.ImageResolveMode = resolveMode
|
||||
|
||||
extraHosts, err := parseExtraHosts(opts[keyGlobalAddHosts])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse additional hosts")
|
||||
}
|
||||
bc.ExtraHosts = extraHosts
|
||||
|
||||
shmSize, err := parseShmSize(opts[keyShmSize])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse shm size")
|
||||
}
|
||||
bc.ShmSize = shmSize
|
||||
|
||||
ulimits, err := parseUlimits(opts[keyUlimit])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse ulimit")
|
||||
}
|
||||
bc.Ulimits = ulimits
|
||||
|
||||
defaultNetMode, err := parseNetMode(opts[keyForceNetwork])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bc.NetworkMode = defaultNetMode
|
||||
|
||||
var ignoreCache []string
|
||||
if v, ok := opts[keyNoCache]; ok {
|
||||
if v == "" {
|
||||
ignoreCache = []string{} // means all stages
|
||||
} else {
|
||||
ignoreCache = strings.Split(v, ",")
|
||||
}
|
||||
}
|
||||
bc.ignoreCache = ignoreCache
|
||||
|
||||
multiPlatform := len(targetPlatforms) > 1
|
||||
if v := opts[keyMultiPlatformArg]; v != "" {
|
||||
opts[keyMultiPlatform] = v
|
||||
}
|
||||
if v := opts[keyMultiPlatform]; v != "" {
|
||||
b, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return errors.Errorf("invalid boolean value for multi-platform: %s", v)
|
||||
}
|
||||
if !b && multiPlatform {
|
||||
return errors.Errorf("conflicting config: returning multiple target platforms is not allowed")
|
||||
}
|
||||
multiPlatform = b
|
||||
}
|
||||
bc.MultiPlatformRequested = multiPlatform
|
||||
|
||||
var cacheImports []client.CacheOptionsEntry
|
||||
// new API
|
||||
if cacheImportsStr := opts[keyCacheImports]; cacheImportsStr != "" {
|
||||
var cacheImportsUM []controlapi.CacheOptionsEntry
|
||||
if err := json.Unmarshal([]byte(cacheImportsStr), &cacheImportsUM); err != nil {
|
||||
return errors.Wrapf(err, "failed to unmarshal %s (%q)", keyCacheImports, cacheImportsStr)
|
||||
}
|
||||
for _, um := range cacheImportsUM {
|
||||
cacheImports = append(cacheImports, client.CacheOptionsEntry{Type: um.Type, Attrs: um.Attrs})
|
||||
}
|
||||
}
|
||||
// old API
|
||||
if cacheFromStr := opts[keyCacheFrom]; cacheFromStr != "" {
|
||||
cacheFrom := strings.Split(cacheFromStr, ",")
|
||||
for _, s := range cacheFrom {
|
||||
im := client.CacheOptionsEntry{
|
||||
Type: "registry",
|
||||
Attrs: map[string]string{
|
||||
"ref": s,
|
||||
},
|
||||
}
|
||||
// FIXME(AkihiroSuda): skip append if already exists
|
||||
cacheImports = append(cacheImports, im)
|
||||
}
|
||||
}
|
||||
bc.CacheImports = cacheImports
|
||||
|
||||
epoch, err := parseSourceDateEpoch(opts[keySourceDateEpoch])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bc.Epoch = epoch
|
||||
|
||||
attests, err := attestations.Parse(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if attrs, ok := attests[attestations.KeyTypeSbom]; ok {
|
||||
src, ok := attrs["generator"]
|
||||
if !ok {
|
||||
return errors.Errorf("sbom scanner cannot be empty")
|
||||
}
|
||||
ref, err := reference.ParseNormalizedNamed(src)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse sbom scanner %s", src)
|
||||
}
|
||||
ref = reference.TagNameOnly(ref)
|
||||
bc.SBOM = &SBOM{
|
||||
Generator: ref.String(),
|
||||
}
|
||||
}
|
||||
|
||||
bc.BuildArgs = filter(opts, buildArgPrefix)
|
||||
bc.Labels = filter(opts, labelPrefix)
|
||||
bc.CacheIDNamespace = opts[keyCacheNSArg]
|
||||
bc.CgroupParent = opts[keyCgroupParent]
|
||||
bc.Target = opts[keyTarget]
|
||||
|
||||
if v, ok := opts[keyHostnameArg]; ok && len(v) > 0 {
|
||||
opts[keyHostname] = v
|
||||
}
|
||||
bc.Hostname = opts[keyHostname]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bc *Client) buildContext(ctx context.Context) (*buildContext, error) {
|
||||
bctx, err := bc.g.Do(ctx, "initcontext", func(ctx context.Context) (interface{}, error) {
|
||||
if bc.bctx != nil {
|
||||
return bc.bctx, nil
|
||||
}
|
||||
bctx, err := bc.initContext(ctx)
|
||||
if err == nil {
|
||||
bc.bctx = bctx
|
||||
}
|
||||
return bctx, err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bctx.(*buildContext), nil
|
||||
}
|
||||
|
||||
func (bc *Client) ReadEntrypoint(ctx context.Context, opts ...llb.LocalOption) (*Source, error) {
|
||||
bctx, err := bc.buildContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var src *llb.State
|
||||
|
||||
if !bctx.forceLocalDockerfile {
|
||||
if bctx.dockerfile != nil {
|
||||
src = bctx.dockerfile
|
||||
}
|
||||
}
|
||||
|
||||
if src == nil {
|
||||
name := "load build definition from " + bctx.filename
|
||||
|
||||
filenames := []string{bctx.filename, bctx.filename + ".dockerignore"}
|
||||
|
||||
// dockerfile is also supported casing moby/moby#10858
|
||||
if path.Base(bctx.filename) == DefaultDockerfileName {
|
||||
filenames = append(filenames, path.Join(path.Dir(bctx.filename), strings.ToLower(DefaultDockerfileName)))
|
||||
}
|
||||
|
||||
opts = append([]llb.LocalOption{
|
||||
llb.FollowPaths(filenames),
|
||||
llb.SessionID(bc.bopts.SessionID),
|
||||
llb.SharedKeyHint(bctx.dockerfileLocalName),
|
||||
WithInternalName(name),
|
||||
llb.Differ(llb.DiffNone, false),
|
||||
}, opts...)
|
||||
|
||||
lsrc := llb.Local(bctx.dockerfileLocalName, opts...)
|
||||
src = &lsrc
|
||||
}
|
||||
|
||||
def, err := src.Marshal(ctx, bc.marshalOpts()...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to marshal local source")
|
||||
}
|
||||
|
||||
defVtx, err := def.Head()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := bc.client.Solve(ctx, client.SolveRequest{
|
||||
Definition: def.ToPB(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to resolve dockerfile")
|
||||
}
|
||||
|
||||
ref, err := res.SingleRef()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dt, err := ref.ReadFile(ctx, client.ReadRequest{
|
||||
Filename: bctx.filename,
|
||||
})
|
||||
if err != nil {
|
||||
if path.Base(bctx.filename) == DefaultDockerfileName {
|
||||
var err1 error
|
||||
dt, err1 = ref.ReadFile(ctx, client.ReadRequest{
|
||||
Filename: path.Join(path.Dir(bctx.filename), strings.ToLower(DefaultDockerfileName)),
|
||||
})
|
||||
if err1 == nil {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to read dockerfile")
|
||||
}
|
||||
}
|
||||
smap := llb.NewSourceMap(src, bctx.filename, dt)
|
||||
smap.Definition = def
|
||||
|
||||
dt, err = ref.ReadFile(ctx, client.ReadRequest{
|
||||
Filename: bctx.filename + ".dockerignore",
|
||||
})
|
||||
if err == nil {
|
||||
bc.dockerignore = dt
|
||||
}
|
||||
|
||||
return &Source{
|
||||
SourceMap: smap,
|
||||
Warn: func(ctx context.Context, msg string, opts client.WarnOpts) {
|
||||
if opts.Level == 0 {
|
||||
opts.Level = 1
|
||||
}
|
||||
if opts.SourceInfo == nil {
|
||||
opts.SourceInfo = &pb.SourceInfo{
|
||||
Data: smap.Data,
|
||||
Filename: smap.Filename,
|
||||
Definition: smap.Definition.ToPB(),
|
||||
}
|
||||
}
|
||||
bc.client.Warn(ctx, defVtx, msg, opts)
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (bc *Client) MainContext(ctx context.Context, opts ...llb.LocalOption) (*llb.State, error) {
|
||||
bctx, err := bc.buildContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if bctx.context != nil {
|
||||
return bctx.context, nil
|
||||
}
|
||||
|
||||
if bc.dockerignore == nil {
|
||||
st := llb.Local(bctx.contextLocalName,
|
||||
llb.SessionID(bc.bopts.SessionID),
|
||||
llb.FollowPaths([]string{DefaultDockerignoreName}),
|
||||
llb.SharedKeyHint(bctx.contextLocalName+"-"+DefaultDockerignoreName),
|
||||
WithInternalName("load "+DefaultDockerignoreName),
|
||||
llb.Differ(llb.DiffNone, false),
|
||||
)
|
||||
def, err := st.Marshal(ctx, bc.marshalOpts()...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := bc.client.Solve(ctx, client.SolveRequest{
|
||||
Definition: def.ToPB(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ref, err := res.SingleRef()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dt, _ := ref.ReadFile(ctx, client.ReadRequest{ // ignore error
|
||||
Filename: DefaultDockerignoreName,
|
||||
})
|
||||
if dt == nil {
|
||||
dt = []byte{}
|
||||
}
|
||||
bc.dockerignore = dt
|
||||
}
|
||||
|
||||
var excludes []string
|
||||
if len(bc.dockerignore) != 0 {
|
||||
excludes, err = dockerignore.ReadAll(bytes.NewBuffer(bc.dockerignore))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse dockerignore")
|
||||
}
|
||||
}
|
||||
|
||||
opts = append([]llb.LocalOption{
|
||||
llb.SessionID(bc.bopts.SessionID),
|
||||
llb.ExcludePatterns(excludes),
|
||||
llb.SharedKeyHint(bctx.contextLocalName),
|
||||
WithInternalName("load build context"),
|
||||
}, opts...)
|
||||
|
||||
st := llb.Local(bctx.contextLocalName, opts...)
|
||||
|
||||
return &st, nil
|
||||
}
|
||||
|
||||
func (bc *Client) NamedContext(ctx context.Context, name string, opt ContextOpt) (*llb.State, *image.Image, error) {
|
||||
named, err := reference.ParseNormalizedNamed(name)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "invalid context name %s", name)
|
||||
}
|
||||
name = strings.TrimSuffix(reference.FamiliarString(named), ":latest")
|
||||
|
||||
pp := platforms.DefaultSpec()
|
||||
if opt.Platform != nil {
|
||||
pp = *opt.Platform
|
||||
}
|
||||
pname := name + "::" + platforms.Format(platforms.Normalize(pp))
|
||||
st, img, err := bc.namedContext(ctx, name, pname, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if st != nil {
|
||||
return st, img, nil
|
||||
}
|
||||
return bc.namedContext(ctx, name, name, opt)
|
||||
}
|
||||
|
||||
func (bc *Client) IsNoCache(name string) bool {
|
||||
if len(bc.ignoreCache) == 0 {
|
||||
return bc.ignoreCache != nil
|
||||
}
|
||||
for _, n := range bc.ignoreCache {
|
||||
if strings.EqualFold(n, name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func WithInternalName(name string) llb.ConstraintsOpt {
|
||||
return llb.WithCustomName("[internal] " + name)
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
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
|
||||
}
|
231
vendor/github.com/moby/buildkit/frontend/dockerui/namedcontext.go
generated
vendored
Normal file
231
vendor/github.com/moby/buildkit/frontend/dockerui/namedcontext.go
generated
vendored
Normal file
|
@ -0,0 +1,231 @@
|
|||
package dockerui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
||||
"github.com/moby/buildkit/exporter/containerimage/image"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/dockerignore"
|
||||
"github.com/moby/buildkit/frontend/gateway/client"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
contextPrefix = "context:"
|
||||
inputMetadataPrefix = "input-metadata:"
|
||||
)
|
||||
|
||||
func (bc *Client) namedContext(ctx context.Context, name string, nameWithPlatform string, opt ContextOpt) (*llb.State, *image.Image, error) {
|
||||
opts := bc.bopts.Opts
|
||||
v, ok := opts[contextPrefix+nameWithPlatform]
|
||||
if !ok {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
vv := strings.SplitN(v, ":", 2)
|
||||
if len(vv) != 2 {
|
||||
return nil, nil, errors.Errorf("invalid context specifier %s for %s", v, nameWithPlatform)
|
||||
}
|
||||
// allow git@ without protocol for SSH URLs for backwards compatibility
|
||||
if strings.HasPrefix(vv[0], "git@") {
|
||||
vv[0] = "git"
|
||||
}
|
||||
switch vv[0] {
|
||||
case "docker-image":
|
||||
ref := strings.TrimPrefix(vv[1], "//")
|
||||
if ref == EmptyImageName {
|
||||
st := llb.Scratch()
|
||||
return &st, nil, nil
|
||||
}
|
||||
|
||||
imgOpt := []llb.ImageOption{
|
||||
llb.WithCustomName("[context " + nameWithPlatform + "] " + ref),
|
||||
}
|
||||
if opt.Platform != nil {
|
||||
imgOpt = append(imgOpt, llb.Platform(*opt.Platform))
|
||||
}
|
||||
|
||||
named, err := reference.ParseNormalizedNamed(ref)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
named = reference.TagNameOnly(named)
|
||||
|
||||
_, data, err := bc.client.ResolveImageConfig(ctx, named.String(), llb.ResolveImageConfigOpt{
|
||||
Platform: opt.Platform,
|
||||
ResolveMode: opt.ResolveMode,
|
||||
LogName: fmt.Sprintf("[context %s] load metadata for %s", nameWithPlatform, ref),
|
||||
ResolverType: llb.ResolverTypeRegistry,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var img image.Image
|
||||
if err := json.Unmarshal(data, &img); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
img.Created = nil
|
||||
|
||||
st := llb.Image(ref, imgOpt...)
|
||||
st, err = st.WithImageConfig(data)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &st, &img, nil
|
||||
case "git":
|
||||
st, ok := DetectGitContext(v, true)
|
||||
if !ok {
|
||||
return nil, nil, errors.Errorf("invalid git context %s", v)
|
||||
}
|
||||
return st, nil, nil
|
||||
case "http", "https":
|
||||
st, ok := DetectGitContext(v, true)
|
||||
if !ok {
|
||||
httpst := llb.HTTP(v, llb.WithCustomName("[context "+nameWithPlatform+"] "+v))
|
||||
st = &httpst
|
||||
}
|
||||
return st, nil, nil
|
||||
case "oci-layout":
|
||||
refSpec := strings.TrimPrefix(vv[1], "//")
|
||||
ref, err := reference.Parse(refSpec)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "could not parse oci-layout reference %q", refSpec)
|
||||
}
|
||||
named, ok := ref.(reference.Named)
|
||||
if !ok {
|
||||
return nil, nil, errors.Errorf("oci-layout reference %q has no name", ref.String())
|
||||
}
|
||||
dgstd, ok := named.(reference.Digested)
|
||||
if !ok {
|
||||
return nil, nil, errors.Errorf("oci-layout reference %q has no digest", named.String())
|
||||
}
|
||||
|
||||
// for the dummy ref primarily used in log messages, we can use the
|
||||
// original name, since the store key may not be significant
|
||||
dummyRef, err := reference.ParseNormalizedNamed(name)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "could not parse oci-layout reference %q", name)
|
||||
}
|
||||
dummyRef, err = reference.WithDigest(dummyRef, dgstd.Digest())
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "could not wrap %q with digest", name)
|
||||
}
|
||||
|
||||
_, data, err := bc.client.ResolveImageConfig(ctx, dummyRef.String(), llb.ResolveImageConfigOpt{
|
||||
Platform: opt.Platform,
|
||||
ResolveMode: opt.ResolveMode,
|
||||
LogName: fmt.Sprintf("[context %s] load metadata for %s", nameWithPlatform, dummyRef.String()),
|
||||
ResolverType: llb.ResolverTypeOCILayout,
|
||||
Store: llb.ResolveImageConfigOptStore{
|
||||
SessionID: bc.bopts.SessionID,
|
||||
StoreID: named.Name(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var img image.Image
|
||||
if err := json.Unmarshal(data, &img); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "could not parse oci-layout image config")
|
||||
}
|
||||
|
||||
ociOpt := []llb.OCILayoutOption{
|
||||
llb.WithCustomName("[context " + nameWithPlatform + "] OCI load from client"),
|
||||
llb.OCIStore(bc.bopts.SessionID, named.Name()),
|
||||
}
|
||||
if opt.Platform != nil {
|
||||
ociOpt = append(ociOpt, llb.Platform(*opt.Platform))
|
||||
}
|
||||
st := llb.OCILayout(
|
||||
dummyRef.String(),
|
||||
ociOpt...,
|
||||
)
|
||||
st, err = st.WithImageConfig(data)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &st, &img, nil
|
||||
case "local":
|
||||
st := llb.Local(vv[1],
|
||||
llb.SessionID(bc.bopts.SessionID),
|
||||
llb.FollowPaths([]string{DefaultDockerignoreName}),
|
||||
llb.SharedKeyHint("context:"+nameWithPlatform+"-"+DefaultDockerignoreName),
|
||||
llb.WithCustomName("[context "+nameWithPlatform+"] load "+DefaultDockerignoreName),
|
||||
llb.Differ(llb.DiffNone, false),
|
||||
)
|
||||
def, err := st.Marshal(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
res, err := bc.client.Solve(ctx, client.SolveRequest{
|
||||
Evaluate: true,
|
||||
Definition: def.ToPB(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ref, err := res.SingleRef()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var excludes []string
|
||||
if !opt.NoDockerignore {
|
||||
dt, _ := ref.ReadFile(ctx, client.ReadRequest{
|
||||
Filename: DefaultDockerignoreName,
|
||||
}) // error ignored
|
||||
|
||||
if len(dt) != 0 {
|
||||
excludes, err = dockerignore.ReadAll(bytes.NewBuffer(dt))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
st = llb.Local(vv[1],
|
||||
llb.WithCustomName("[context "+nameWithPlatform+"] load from client"),
|
||||
llb.SessionID(bc.bopts.SessionID),
|
||||
llb.SharedKeyHint("context:"+nameWithPlatform),
|
||||
llb.ExcludePatterns(excludes),
|
||||
)
|
||||
return &st, nil, nil
|
||||
case "input":
|
||||
inputs, err := bc.client.Inputs(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
st, ok := inputs[vv[1]]
|
||||
if !ok {
|
||||
return nil, nil, errors.Errorf("invalid input %s for %s", vv[1], nameWithPlatform)
|
||||
}
|
||||
md, ok := opts[inputMetadataPrefix+vv[1]]
|
||||
if ok {
|
||||
m := make(map[string][]byte)
|
||||
if err := json.Unmarshal([]byte(md), &m); err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "failed to parse input metadata %s", md)
|
||||
}
|
||||
var img *image.Image
|
||||
if dtic, ok := m[exptypes.ExporterImageConfigKey]; ok {
|
||||
st, err = st.WithImageConfig(dtic)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := json.Unmarshal(dtic, &img); err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "failed to parse image config for %s", nameWithPlatform)
|
||||
}
|
||||
}
|
||||
return &st, img, nil
|
||||
}
|
||||
return &st, nil, nil
|
||||
default:
|
||||
return nil, nil, errors.Errorf("unsupported context source %s for %s", vv[0], nameWithPlatform)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package dockerui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/moby/buildkit/frontend/gateway/client"
|
||||
"github.com/moby/buildkit/frontend/subrequests"
|
||||
"github.com/moby/buildkit/frontend/subrequests/outline"
|
||||
"github.com/moby/buildkit/frontend/subrequests/targets"
|
||||
"github.com/moby/buildkit/solver/errdefs"
|
||||
)
|
||||
|
||||
const (
|
||||
keyRequestID = "requestid"
|
||||
)
|
||||
|
||||
type RequestHandler struct {
|
||||
Outline func(context.Context) (*outline.Outline, error)
|
||||
ListTargets func(context.Context) (*targets.List, error)
|
||||
AllowOther bool
|
||||
}
|
||||
|
||||
func (bc *Client) HandleSubrequest(ctx context.Context, h RequestHandler) (*client.Result, bool, error) {
|
||||
req, ok := bc.bopts.Opts[keyRequestID]
|
||||
if !ok {
|
||||
return nil, false, nil
|
||||
}
|
||||
switch req {
|
||||
case subrequests.RequestSubrequestsDescribe:
|
||||
res, err := describe(h)
|
||||
return res, true, err
|
||||
case outline.SubrequestsOutlineDefinition.Name:
|
||||
if f := h.Outline; f != nil {
|
||||
o, err := f(ctx)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if o == nil {
|
||||
return nil, true, nil
|
||||
}
|
||||
res, err := o.ToResult()
|
||||
return res, true, err
|
||||
}
|
||||
case targets.SubrequestsTargetsDefinition.Name:
|
||||
if f := h.ListTargets; f != nil {
|
||||
targets, err := f(ctx)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if targets == nil {
|
||||
return nil, true, nil
|
||||
}
|
||||
res, err := targets.ToResult()
|
||||
return res, true, err
|
||||
}
|
||||
}
|
||||
if h.AllowOther {
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, errdefs.NewUnsupportedSubrequestError(req)
|
||||
}
|
||||
|
||||
func describe(h RequestHandler) (*client.Result, error) {
|
||||
all := []subrequests.Request{}
|
||||
if h.Outline != nil {
|
||||
all = append(all, outline.SubrequestsOutlineDefinition)
|
||||
}
|
||||
if h.ListTargets != nil {
|
||||
all = append(all, targets.SubrequestsTargetsDefinition)
|
||||
}
|
||||
all = append(all, subrequests.SubrequestsDescribeDefinition)
|
||||
dt, err := json.MarshalIndent(all, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(nil)
|
||||
if err := subrequests.PrintDescribe(dt, b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := client.NewResult()
|
||||
res.Metadata = map[string][]byte{
|
||||
"result.json": dt,
|
||||
"result.txt": b.Bytes(),
|
||||
"version": []byte(subrequests.SubrequestsDescribeDefinition.Version),
|
||||
}
|
||||
return res, nil
|
||||
}
|
|
@ -510,6 +510,9 @@ github.com/moby/buildkit/client/ociindex
|
|||
github.com/moby/buildkit/cmd/buildkitd/config
|
||||
github.com/moby/buildkit/exporter/containerimage/exptypes
|
||||
github.com/moby/buildkit/exporter/containerimage/image
|
||||
github.com/moby/buildkit/frontend/attestations
|
||||
github.com/moby/buildkit/frontend/dockerfile/dockerignore
|
||||
github.com/moby/buildkit/frontend/dockerui
|
||||
github.com/moby/buildkit/frontend/gateway/client
|
||||
github.com/moby/buildkit/frontend/gateway/grpcclient
|
||||
github.com/moby/buildkit/frontend/gateway/pb
|
||||
|
|
Loading…
Reference in New Issue