From 62a21520ea344cf0e48f76559b4a2d3c998ce3f7 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 12 Apr 2023 10:06:25 +0100 Subject: [PATCH 1/2] vendor: update buildkit to master@333ee9158128 Signed-off-by: Justin Chadwell --- driver/remote/driver.go | 5 +- go.mod | 3 +- go.sum | 6 +- .../github.com/moby/buildkit/client/client.go | 135 +++++++++++++----- vendor/modules.txt | 4 +- 5 files changed, 109 insertions(+), 44 deletions(-) diff --git a/driver/remote/driver.go b/driver/remote/driver.go index a931e298..eaafc2ac 100644 --- a/driver/remote/driver.go +++ b/driver/remote/driver.go @@ -78,7 +78,10 @@ func (d *Driver) Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error { func (d *Driver) Client(ctx context.Context) (*client.Client, error) { opts := []client.ClientOpt{} if d.tlsOpts != nil { - opts = append(opts, client.WithCredentials(d.tlsOpts.serverName, d.tlsOpts.caCert, d.tlsOpts.cert, d.tlsOpts.key)) + opts = append(opts, []client.ClientOpt{ + client.WithServerConfig(d.tlsOpts.serverName, d.tlsOpts.caCert), + client.WithCredentials(d.tlsOpts.cert, d.tlsOpts.key), + }...) } return client.New(ctx, d.InitConfig.EndpointAddr, opts...) diff --git a/go.mod b/go.mod index 2a15ef9b..b02a3a08 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/google/uuid v1.3.0 github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840 github.com/hashicorp/hcl/v2 v2.8.2 - github.com/moby/buildkit v0.11.0-rc3.0.20230330090027-8b7bcb900d3c + github.com/moby/buildkit v0.11.0-rc3.0.20230411142536-333ee9158128 github.com/moby/sys/mountinfo v0.6.2 github.com/moby/sys/signal v0.7.0 github.com/morikuni/aec v1.0.0 @@ -119,7 +119,6 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/klauspost/compress v1.16.0 // indirect - github.com/kr/pretty v0.3.0 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect diff --git a/go.sum b/go.sum index 037f00ed..8fa4bd6b 100644 --- a/go.sum +++ b/go.sum @@ -384,7 +384,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -408,8 +407,8 @@ github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzC github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/moby/buildkit v0.11.0-rc3.0.20230330090027-8b7bcb900d3c h1:JZvvWzulcnA2G4c/gJiSIqKDUoBjctYw2WMuS+XJexU= -github.com/moby/buildkit v0.11.0-rc3.0.20230330090027-8b7bcb900d3c/go.mod h1:NehrLo0nsnhS/+X+XyhU4LNucb1ndYXgPBOx/JNWVDA= +github.com/moby/buildkit v0.11.0-rc3.0.20230411142536-333ee9158128 h1:MpK1e4TlETVYELLbzaxmbvQrftq1TWYoOftFiSNk/iE= +github.com/moby/buildkit v0.11.0-rc3.0.20230411142536-333ee9158128/go.mod h1:GwK84qTEVfkyvAhd6aET84FRzND+lrQZC0pTesljST0= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo= @@ -496,7 +495,6 @@ github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0ua github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/vendor/github.com/moby/buildkit/client/client.go b/vendor/github.com/moby/buildkit/client/client.go index c02162bc..8ef7cab2 100644 --- a/vendor/github.com/moby/buildkit/client/client.go +++ b/vendor/github.com/moby/buildkit/client/client.go @@ -45,8 +45,6 @@ func New(ctx context.Context, address string, opts ...ClientOpt) (*Client, error grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(defaults.DefaultMaxSendMsgSize)), } needDialer := true - needWithInsecure := true - tlsServerName := "" var unary []grpc.UnaryClientInterceptor var stream []grpc.StreamClientInterceptor @@ -56,19 +54,17 @@ func New(ctx context.Context, address string, opts ...ClientOpt) (*Client, error var tracerDelegate TracerDelegate var sessionDialer func(context.Context, string, map[string][]string) (net.Conn, error) var customDialOptions []grpc.DialOption + var creds *withCredentials for _, o := range opts { if _, ok := o.(*withFailFast); ok { gopts = append(gopts, grpc.FailOnNonTempDialError(true)) } if credInfo, ok := o.(*withCredentials); ok { - opt, err := loadCredentials(credInfo) - if err != nil { - return nil, err + if creds == nil { + creds = &withCredentials{} } - gopts = append(gopts, opt) - needWithInsecure = false - tlsServerName = credInfo.ServerName + creds = creds.merge(credInfo) } if wt, ok := o.(*withTracer); ok { customTracer = true @@ -89,6 +85,16 @@ func New(ctx context.Context, address string, opts ...ClientOpt) (*Client, error } } + if creds == nil { + gopts = append(gopts, grpc.WithTransportCredentials(insecure.NewCredentials())) + } else { + credOpts, err := loadCredentials(creds) + if err != nil { + return nil, err + } + gopts = append(gopts, credOpts) + } + if !customTracer { if span := trace.SpanFromContext(ctx); span.SpanContext().IsValid() { tracerProvider = span.TracerProvider() @@ -108,9 +114,6 @@ func New(ctx context.Context, address string, opts ...ClientOpt) (*Client, error } gopts = append(gopts, grpc.WithContextDialer(dialFn)) } - if needWithInsecure { - gopts = append(gopts, grpc.WithTransportCredentials(insecure.NewCredentials())) - } if address == "" { address = appdefaults.Address } @@ -122,7 +125,10 @@ func New(ctx context.Context, address string, opts ...ClientOpt) (*Client, error // ref: https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.3 // - However, when TLS specified, grpc-go requires it must match // with its servername specified for certificate validation. - authority := tlsServerName + var authority string + if creds != nil && creds.serverName != "" { + authority = creds.serverName + } if authority == "" { // authority as hostname from target address uri, err := url.Parse(address) @@ -201,47 +207,108 @@ func WithContextDialer(df func(context.Context, string) (net.Conn, error)) Clien } type withCredentials struct { - ServerName string - CACert string - Cert string - Key string + // server options + serverName string + caCert string + caCertSystem bool + + // client options + cert string + key string +} + +func (opts *withCredentials) merge(opts2 *withCredentials) *withCredentials { + result := *opts + if opts2 == nil { + return &result + } + + // server options + if opts2.serverName != "" { + result.serverName = opts2.serverName + } + if opts2.caCert != "" { + result.caCert = opts2.caCert + } + if opts2.caCertSystem { + result.caCertSystem = opts2.caCertSystem + } + + // client options + if opts2.cert != "" { + result.cert = opts2.cert + } + if opts2.key != "" { + result.key = opts2.key + } + + return &result } func (*withCredentials) isClientOpt() {} // WithCredentials configures the TLS parameters of the client. // Arguments: -// * serverName: specifies the name of the target server -// * ca: specifies the filepath of the CA certificate to use for verification -// * cert: specifies the filepath of the client certificate -// * key: specifies the filepath of the client key -func WithCredentials(serverName, ca, cert, key string) ClientOpt { - return &withCredentials{serverName, ca, cert, key} +// * cert: specifies the filepath of the client certificate +// * key: specifies the filepath of the client key +func WithCredentials(cert, key string) ClientOpt { + return &withCredentials{ + cert: cert, + key: key, + } +} + +// WithServerConfig configures the TLS parameters to connect to the server. +// Arguments: +// * serverName: specifies the server name to verify the hostname +// * caCert: specifies the filepath of the CA certificate +func WithServerConfig(serverName, caCert string) ClientOpt { + return &withCredentials{ + serverName: serverName, + caCert: caCert, + } +} + +// WithServerConfigSystem configures the TLS parameters to connect to the +// server, using the system's certificate pool. +func WithServerConfigSystem(serverName string) ClientOpt { + return &withCredentials{ + serverName: serverName, + caCertSystem: true, + } } func loadCredentials(opts *withCredentials) (grpc.DialOption, error) { - ca, err := os.ReadFile(opts.CACert) - if err != nil { - return nil, errors.Wrap(err, "could not read ca certificate") + cfg := &tls.Config{} + + if opts.caCertSystem { + cfg.RootCAs, _ = x509.SystemCertPool() + } + if cfg.RootCAs == nil { + cfg.RootCAs = x509.NewCertPool() } - certPool := x509.NewCertPool() - if ok := certPool.AppendCertsFromPEM(ca); !ok { - return nil, errors.New("failed to append ca certs") + if opts.caCert != "" { + ca, err := os.ReadFile(opts.caCert) + if err != nil { + return nil, errors.Wrap(err, "could not read ca certificate") + } + if ok := cfg.RootCAs.AppendCertsFromPEM(ca); !ok { + return nil, errors.New("failed to append ca certs") + } } - cfg := &tls.Config{ - ServerName: opts.ServerName, - RootCAs: certPool, + if opts.serverName != "" { + cfg.ServerName = opts.serverName } // we will produce an error if the user forgot about either cert or key if at least one is specified - if opts.Cert != "" || opts.Key != "" { - cert, err := tls.LoadX509KeyPair(opts.Cert, opts.Key) + if opts.cert != "" || opts.key != "" { + cert, err := tls.LoadX509KeyPair(opts.cert, opts.key) if err != nil { return nil, errors.Wrap(err, "could not read certificate/key") } - cfg.Certificates = []tls.Certificate{cert} + cfg.Certificates = append(cfg.Certificates, cert) } return grpc.WithTransportCredentials(credentials.NewTLS(cfg)), nil diff --git a/vendor/modules.txt b/vendor/modules.txt index 610a2937..671810f9 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -475,8 +475,6 @@ github.com/klauspost/compress/internal/cpuinfo github.com/klauspost/compress/internal/snapref github.com/klauspost/compress/zstd github.com/klauspost/compress/zstd/internal/xxhash -# github.com/kr/pretty v0.3.0 -## explicit; go 1.12 # github.com/mailru/easyjson v0.7.6 ## explicit; go 1.12 github.com/mailru/easyjson/buffer @@ -497,7 +495,7 @@ github.com/mitchellh/go-wordwrap # github.com/mitchellh/mapstructure v1.5.0 ## explicit; go 1.14 github.com/mitchellh/mapstructure -# github.com/moby/buildkit v0.11.0-rc3.0.20230330090027-8b7bcb900d3c +# github.com/moby/buildkit v0.11.0-rc3.0.20230411142536-333ee9158128 ## explicit; go 1.20 github.com/moby/buildkit/api/services/control github.com/moby/buildkit/api/types From 871f865ac87c84176616e9fd91d4514c70653399 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 12 Apr 2023 10:57:54 +0100 Subject: [PATCH 2/2] bake: update ReadRemoteFiles to use buildkit api Signed-off-by: Justin Chadwell --- bake/bake.go | 3 - bake/remote.go | 41 +- .../buildkit/frontend/attestations/parse.go | 81 +++ .../dockerfile/dockerignore/dockerignore.go | 65 +++ .../moby/buildkit/frontend/dockerui/attr.go | 138 +++++ .../moby/buildkit/frontend/dockerui/build.go | 114 ++++ .../moby/buildkit/frontend/dockerui/config.go | 497 ++++++++++++++++++ .../buildkit/frontend/dockerui/context.go | 194 +++++++ .../frontend/dockerui/namedcontext.go | 231 ++++++++ .../buildkit/frontend/dockerui/requests.go | 91 ++++ vendor/modules.txt | 3 + 11 files changed, 1417 insertions(+), 41 deletions(-) create mode 100644 vendor/github.com/moby/buildkit/frontend/attestations/parse.go create mode 100644 vendor/github.com/moby/buildkit/frontend/dockerfile/dockerignore/dockerignore.go create mode 100644 vendor/github.com/moby/buildkit/frontend/dockerui/attr.go create mode 100644 vendor/github.com/moby/buildkit/frontend/dockerui/build.go create mode 100644 vendor/github.com/moby/buildkit/frontend/dockerui/config.go create mode 100644 vendor/github.com/moby/buildkit/frontend/dockerui/context.go create mode 100644 vendor/github.com/moby/buildkit/frontend/dockerui/namedcontext.go create mode 100644 vendor/github.com/moby/buildkit/frontend/dockerui/requests.go diff --git a/bake/bake.go b/bake/bake.go index e8073202..f0e29e40 100644 --- a/bake/bake.go +++ b/bake/bake.go @@ -27,9 +27,6 @@ import ( ) var ( - httpPrefix = regexp.MustCompile(`^https?://`) - gitURLPathWithFragmentSuffix = regexp.MustCompile(`\.git(?:#.+)?$`) - validTargetNameChars = `[a-zA-Z0-9_-]+` targetNamePattern = regexp.MustCompile(`^` + validTargetNameChars + `$`) ) diff --git a/bake/remote.go b/bake/remote.go index 30292cd8..92d2bb57 100644 --- a/bake/remote.go +++ b/bake/remote.go @@ -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 diff --git a/vendor/github.com/moby/buildkit/frontend/attestations/parse.go b/vendor/github.com/moby/buildkit/frontend/attestations/parse.go new file mode 100644 index 00000000..00de649f --- /dev/null +++ b/vendor/github.com/moby/buildkit/frontend/attestations/parse.go @@ -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) +} diff --git a/vendor/github.com/moby/buildkit/frontend/dockerfile/dockerignore/dockerignore.go b/vendor/github.com/moby/buildkit/frontend/dockerfile/dockerignore/dockerignore.go new file mode 100644 index 00000000..e7f29ae8 --- /dev/null +++ b/vendor/github.com/moby/buildkit/frontend/dockerfile/dockerignore/dockerignore.go @@ -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 +} diff --git a/vendor/github.com/moby/buildkit/frontend/dockerui/attr.go b/vendor/github.com/moby/buildkit/frontend/dockerui/attr.go new file mode 100644 index 00000000..52ec0122 --- /dev/null +++ b/vendor/github.com/moby/buildkit/frontend/dockerui/attr.go @@ -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 +} diff --git a/vendor/github.com/moby/buildkit/frontend/dockerui/build.go b/vendor/github.com/moby/buildkit/frontend/dockerui/build.go new file mode 100644 index 00000000..8fc9bbbf --- /dev/null +++ b/vendor/github.com/moby/buildkit/frontend/dockerui/build.go @@ -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() +} diff --git a/vendor/github.com/moby/buildkit/frontend/dockerui/config.go b/vendor/github.com/moby/buildkit/frontend/dockerui/config.go new file mode 100644 index 00000000..c2584cea --- /dev/null +++ b/vendor/github.com/moby/buildkit/frontend/dockerui/config.go @@ -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) +} diff --git a/vendor/github.com/moby/buildkit/frontend/dockerui/context.go b/vendor/github.com/moby/buildkit/frontend/dockerui/context.go new file mode 100644 index 00000000..3173558f --- /dev/null +++ b/vendor/github.com/moby/buildkit/frontend/dockerui/context.go @@ -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 +} diff --git a/vendor/github.com/moby/buildkit/frontend/dockerui/namedcontext.go b/vendor/github.com/moby/buildkit/frontend/dockerui/namedcontext.go new file mode 100644 index 00000000..28db8488 --- /dev/null +++ b/vendor/github.com/moby/buildkit/frontend/dockerui/namedcontext.go @@ -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) + } +} diff --git a/vendor/github.com/moby/buildkit/frontend/dockerui/requests.go b/vendor/github.com/moby/buildkit/frontend/dockerui/requests.go new file mode 100644 index 00000000..7900a0c7 --- /dev/null +++ b/vendor/github.com/moby/buildkit/frontend/dockerui/requests.go @@ -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 +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 671810f9..e82c49b4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -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