From c737e84f9405142477c763f59c7a161c8b82fd94 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 8 Sep 2025 17:40:31 -0700 Subject: [PATCH 1/2] build: allow client side querystring resolution For the cases where frontend/buildkit doesn't support new Git Querystring format, allow resolving the URL into LLB on client side and then build from input. Note that this produces slightly different provenance where context is not set as string, so added opt-in via environment variable for now. Signed-off-by: Tonis Tiigi --- build/opt.go | 90 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 15 deletions(-) diff --git a/build/opt.go b/build/opt.go index 73227f3ec86b..ea63f5ecc8f6 100644 --- a/build/opt.go +++ b/build/opt.go @@ -10,6 +10,7 @@ import ( "slices" "strconv" "strings" + "sync" "syscall" awsconfig "github.com/aws/aws-sdk-go-v2/config" @@ -29,6 +30,7 @@ import ( "github.com/moby/buildkit/client/llb" "github.com/moby/buildkit/client/ociindex" "github.com/moby/buildkit/exporter/containerimage/exptypes" + "github.com/moby/buildkit/frontend/dockerui" gateway "github.com/moby/buildkit/frontend/gateway/client" "github.com/moby/buildkit/identity" "github.com/moby/buildkit/session" @@ -44,6 +46,15 @@ import ( "github.com/tonistiigi/fsutil" ) +var sendGitQueryAsInput = sync.OnceValue(func() bool { + if v, ok := os.LookupEnv("BUILDX_SEND_GIT_QUERY_AS_INPUT"); ok { + if vv, err := strconv.ParseBool(v); err == nil { + return vv + } + } + return false +}) + func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt *Options, bopts gateway.BuildOpts, cfg *confutil.Config, pw progress.Writer, docker *dockerutil.Client) (_ *client.SolveOpt, release func(), err error) { nodeDriver := node.Driver defers := make([]func(), 0, 2) @@ -298,6 +309,13 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt *O so.Exports = opt.Exports so.Session = slices.Clone(opt.Session) + for k, v := range opt.BuildArgs { + so.FrontendAttrs["build-arg:"+k] = v + } + for k, v := range opt.Labels { + so.FrontendAttrs["label:"+k] = v + } + releaseLoad, err := loadInputs(ctx, nodeDriver, &opt.Inputs, pw, &so) if err != nil { return nil, nil, err @@ -324,12 +342,6 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt *O if opt.NoCache { so.FrontendAttrs["no-cache"] = "" } - for k, v := range opt.BuildArgs { - so.FrontendAttrs["build-arg:"+k] = v - } - for k, v := range opt.Labels { - so.FrontendAttrs["label:"+k] = v - } for k, v := range node.ProxyConfig { if _, ok := opt.BuildArgs[k]; !ok { @@ -472,9 +484,13 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro } target.FrontendAttrs["context"] = inp.ContextPath - gitRef, err := gitutil.ParseURL(inp.ContextPath) - if err == nil && len(gitRef.Query) > 0 { - caps["moby.buildkit.frontend.gitquerystring"] = struct{}{} + if err := processGitURL(inp.ContextPath, "context", target, caps); err != nil { + return nil, err + } + if st, ok := target.FrontendInputs["context"]; ok { + if dockerfileReader == nil && !filepath.IsAbs(inp.DockerfilePath) { + target.FrontendInputs["dockerfile"] = st + } } default: @@ -536,12 +552,7 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro if IsRemoteURL(v.Path) || strings.HasPrefix(v.Path, "docker-image://") || strings.HasPrefix(v.Path, "target:") { target.FrontendAttrs["context:"+k] = v.Path - gitRef, err := gitutil.ParseURL(v.Path) - if err == nil && len(gitRef.Query) > 0 { - if _, ok := caps["moby.buildkit.frontend.gitquerystring"]; !ok { - caps["moby.buildkit.frontend.gitquerystring+forward"] = struct{}{} - } - } + processGitURL(v.Path, "context:"+k, target, caps) continue } @@ -672,6 +683,55 @@ func createTempDockerfile(r io.Reader, multiReader *SyncMultiReader) (string, er return dir, err } +func processGitURL(url string, name string, target *client.SolveOpt, caps map[string]struct{}) error { + gitRef, err := gitutil.ParseURL(url) + if err != nil { + return err + } + if len(gitRef.Query) == 0 { + return nil + } + if !sendGitQueryAsInput() { + capName := "moby.buildkit.frontend.gitquerystring" + if name != "context" { + capName += "+forward" + } + caps[capName] = struct{}{} + return nil + } + + var keepGitDir *bool + if name == "context" { + if v, ok := target.FrontendAttrs["build-arg:BUILDKIT_CONTEXT_KEEP_GIT_DIR"]; ok { + if vv, err := strconv.ParseBool(v); err == nil { + keepGitDir = &vv + } + } + } + + st, ok, err := dockerui.DetectGitContext(url, keepGitDir) + if err != nil { + return err + } + if !ok { + return nil + } + + if target.FrontendInputs == nil { + target.FrontendInputs = make(map[string]llb.State) + } + if name == "context" { + target.FrontendInputs["context"] = *st + delete(target.FrontendAttrs, "context") + } else { + inputName := "git_state_" + name + target.FrontendInputs[inputName] = *st + target.FrontendAttrs[name] = "input:" + inputName + } + + return nil +} + // handle https://github.com/moby/moby/pull/10858 func handleLowercaseDockerfile(dir, p string) string { if filepath.Base(p) != "Dockerfile" { From 5a3afbf839bd7fc625e29ce942effa0c9387329b Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Tue, 9 Sep 2025 11:08:00 -0700 Subject: [PATCH 2/2] build: set original url to attrs when replacef with input Save original URL so it can be picked up from provenance attestation. Signed-off-by: Tonis Tiigi --- build/opt.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/opt.go b/build/opt.go index ea63f5ecc8f6..db5caf901ec6 100644 --- a/build/opt.go +++ b/build/opt.go @@ -723,10 +723,12 @@ func processGitURL(url string, name string, target *client.SolveOpt, caps map[st if name == "context" { target.FrontendInputs["context"] = *st delete(target.FrontendAttrs, "context") + target.FrontendAttrs["input:context"] = url } else { inputName := "git_state_" + name target.FrontendInputs[inputName] = *st target.FrontendAttrs[name] = "input:" + inputName + target.FrontendAttrs["input:"+name] = url } return nil