Merge pull request #1733 from jedevc/use-dockerui-context-detection
Use dockerui context detectionpull/1740/head
						commit
						7d35a3b8d8
					
				@ -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
									
								
								
							
							
										
											65
										
									
									vendor/github.com/moby/buildkit/frontend/dockerfile/dockerignore/dockerignore.go
									
										generated
									
									
										vendored
									
								@ -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
 | 
			
		||||
}
 | 
			
		||||
@ -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
 | 
			
		||||
}
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue