From 7899695fa6a44d1df36711331f69e1a07f0aa2e1 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Mon, 9 Feb 2026 16:29:21 +0100 Subject: [PATCH] history: don't import build package Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- bake/bake.go | 17 +++--- build/localstate.go | 5 +- build/opt.go | 7 ++- build/utils.go | 18 ------ commands/bake.go | 5 +- commands/history/utils.go | 4 +- util/urlutil/urlutil.go | 25 ++++++++ util/urlutil/urlutil_test.go | 110 +++++++++++++++++++++++++++++++++++ 8 files changed, 156 insertions(+), 35 deletions(-) create mode 100644 util/urlutil/urlutil.go create mode 100644 util/urlutil/urlutil_test.go diff --git a/bake/bake.go b/bake/bake.go index 50ea3f175fa2..2633ce357dc0 100644 --- a/bake/bake.go +++ b/bake/bake.go @@ -24,6 +24,7 @@ import ( "github.com/docker/buildx/util/buildflags" "github.com/docker/buildx/util/platformutil" "github.com/docker/buildx/util/progress" + "github.com/docker/buildx/util/urlutil" dockeropts "github.com/docker/cli/opts" hcl "github.com/hashicorp/hcl/v2" "github.com/moby/buildkit/client" @@ -1302,7 +1303,7 @@ func updateContext(t *build.Inputs, inp *Input) { if strings.HasPrefix(v.Path, "cwd://") || strings.HasPrefix(v.Path, "target:") || strings.HasPrefix(v.Path, "docker-image:") { continue } - if build.IsRemoteURL(v.Path) { + if urlutil.IsRemoteURL(v.Path) { continue } st := llb.Scratch().File(llb.Copy(*inp.State, v.Path, "/"), llb.WithCustomNamef("set context %s to %s", k, v.Path)) @@ -1316,7 +1317,7 @@ func updateContext(t *build.Inputs, inp *Input) { if strings.HasPrefix(t.ContextPath, "cwd://") { return } - if build.IsRemoteURL(t.ContextPath) { + if urlutil.IsRemoteURL(t.ContextPath) { return } st := llb.Scratch().File( @@ -1329,10 +1330,10 @@ func updateContext(t *build.Inputs, inp *Input) { } func isRemoteContext(t build.Inputs, inp *Input) bool { - if build.IsRemoteURL(t.ContextPath) { + if urlutil.IsRemoteURL(t.ContextPath) { return true } - if inp != nil && build.IsRemoteURL(inp.URL) && !strings.HasPrefix(t.ContextPath, "cwd://") { + if inp != nil && urlutil.IsRemoteURL(inp.URL) && !strings.HasPrefix(t.ContextPath, "cwd://") { return true } return false @@ -1362,7 +1363,7 @@ func collectLocalPaths(t build.Inputs) []string { } func isLocalPath(p string) (string, bool) { - if build.IsRemoteURL(p) || strings.HasPrefix(p, "target:") || strings.HasPrefix(p, "docker-image:") { + if urlutil.IsRemoteURL(p) || strings.HasPrefix(p, "target:") || strings.HasPrefix(p, "docker-image:") { return "", false } return strings.TrimPrefix(p, "cwd://"), true @@ -1380,7 +1381,7 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) { if t.Context != nil { contextPath = *t.Context } - if !strings.HasPrefix(contextPath, "cwd://") && !build.IsRemoteURL(contextPath) { + if !strings.HasPrefix(contextPath, "cwd://") && !urlutil.IsRemoteURL(contextPath) { contextPath = path.Clean(contextPath) } dockerfilePath := "Dockerfile" @@ -1410,7 +1411,7 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) { if err != nil { return nil, err } - } else if !build.IsRemoteURL(bi.DockerfilePath) && strings.HasPrefix(bi.ContextPath, "cwd://") && (inp != nil && build.IsRemoteURL(inp.URL)) { + } else if !urlutil.IsRemoteURL(bi.DockerfilePath) && strings.HasPrefix(bi.ContextPath, "cwd://") && (inp != nil && urlutil.IsRemoteURL(inp.URL)) { // We don't currently support reading a remote Dockerfile with a local // context when doing a remote invocation because we automatically // derive the dockerfile from the context atm: @@ -1432,7 +1433,7 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) { if v, ok := strings.CutPrefix(bi.ContextPath, "cwd://"); ok { bi.ContextPath = path.Clean(v) } - if !build.IsRemoteURL(bi.ContextPath) && bi.ContextState == nil && !filepath.IsAbs(bi.DockerfilePath) { + if !urlutil.IsRemoteURL(bi.ContextPath) && bi.ContextState == nil && !filepath.IsAbs(bi.DockerfilePath) { bi.DockerfilePath = filepath.Join(bi.ContextPath, bi.DockerfilePath) } for k, v := range bi.NamedContexts { diff --git a/build/localstate.go b/build/localstate.go index 1c8f51e91012..0c29dd94c4b4 100644 --- a/build/localstate.go +++ b/build/localstate.go @@ -6,6 +6,7 @@ import ( "github.com/docker/buildx/builder" "github.com/docker/buildx/localstate" "github.com/docker/buildx/util/confutil" + "github.com/docker/buildx/util/urlutil" "github.com/moby/buildkit/client" ) @@ -16,13 +17,13 @@ func saveLocalState(so *client.SolveOpt, target string, opts Options, node build } lp := opts.Inputs.ContextPath dp := opts.Inputs.DockerfilePath - if dp != "" && !IsRemoteURL(lp) && lp != "-" && dp != "-" { + if dp != "" && !urlutil.IsRemoteURL(lp) && lp != "-" && dp != "-" { dp, err = filepath.Abs(dp) if err != nil { return err } } - if lp != "" && !IsRemoteURL(lp) && lp != "-" { + if lp != "" && !urlutil.IsRemoteURL(lp) && lp != "-" { lp, err = filepath.Abs(lp) if err != nil { return err diff --git a/build/opt.go b/build/opt.go index 1642304b2dd5..d37bc4bbb506 100644 --- a/build/opt.go +++ b/build/opt.go @@ -31,6 +31,7 @@ import ( "github.com/docker/buildx/util/dockerutil" "github.com/docker/buildx/util/osutil" "github.com/docker/buildx/util/progress" + "github.com/docker/buildx/util/urlutil" "github.com/moby/buildkit/client" "github.com/moby/buildkit/client/llb" "github.com/moby/buildkit/client/ociindex" @@ -700,7 +701,7 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro dockerfileDir = filepath.Dir(inp.DockerfilePath) dockerfileName = filepath.Base(inp.DockerfilePath) } - case IsRemoteURL(inp.ContextPath): + case urlutil.IsRemoteURL(inp.ContextPath): if inp.DockerfilePath == "-" { dockerfileReader = inp.InStream.NewReadCloser() } else if filepath.IsAbs(inp.DockerfilePath) { @@ -741,7 +742,7 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro dockerfileName = "Dockerfile" target.FrontendAttrs["dockerfilekey"] = "dockerfile" } - if isHTTPURL(inp.DockerfilePath) { + if urlutil.IsHTTPURL(inp.DockerfilePath) { dockerfileDir, err = createTempDockerfileFromURL(ctx, d, inp.DockerfilePath, pw) if err != nil { return nil, err @@ -811,7 +812,7 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro continue } - if IsRemoteURL(v.Path) || strings.HasPrefix(v.Path, "docker-image://") || strings.HasPrefix(v.Path, "target:") { + if urlutil.IsRemoteURL(v.Path) || strings.HasPrefix(v.Path, "docker-image://") || strings.HasPrefix(v.Path, "target:") { target.FrontendAttrs["context:"+k] = v.Path processGitURL(v.Path, "context:"+k, target, caps) continue diff --git a/build/utils.go b/build/utils.go index bf81b38f2a56..2c9acaf53ddd 100644 --- a/build/utils.go +++ b/build/utils.go @@ -11,7 +11,6 @@ import ( "github.com/docker/buildx/driver" "github.com/docker/cli/opts" - "github.com/moby/buildkit/frontend/dockerfile/dfgitutil" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -25,23 +24,6 @@ const ( mobyHostGatewayName = "host-gateway" ) -// isHTTPURL returns true if the provided str is an HTTP(S) URL by checking if it -// has a http:// or https:// scheme. No validation is performed to verify if the -// URL is well-formed. -func isHTTPURL(str string) bool { - return strings.HasPrefix(str, "https://") || strings.HasPrefix(str, "http://") -} - -func IsRemoteURL(c string) bool { - if isHTTPURL(c) { - return true - } - if _, ok, _ := dfgitutil.ParseGitRef(c); ok { - return true - } - return false -} - func isArchive(header []byte) bool { for _, m := range [][]byte{ {0x42, 0x5A, 0x68}, // bzip2 diff --git a/commands/bake.go b/commands/bake.go index 78d5e5bfe82b..2b23032b6b98 100644 --- a/commands/bake.go +++ b/commands/bake.go @@ -33,6 +33,7 @@ import ( "github.com/docker/buildx/util/osutil" "github.com/docker/buildx/util/progress" "github.com/docker/buildx/util/tracing" + "github.com/docker/buildx/util/urlutil" "github.com/docker/cli/cli/command" "github.com/moby/buildkit/identity" "github.com/moby/buildkit/session/auth/authprovider" @@ -613,11 +614,11 @@ func saveLocalStateGroup(dockerCli command.Cli, in bakeOptions, targets []string // from the command line arguments. func bakeArgs(args []string) (url, cmdContext string, targets []string) { cmdContext, targets = "cwd://", args - if len(targets) == 0 || !build.IsRemoteURL(targets[0]) { + if len(targets) == 0 || !urlutil.IsRemoteURL(targets[0]) { return url, cmdContext, targets } url, targets = targets[0], targets[1:] - if len(targets) == 0 || !build.IsRemoteURL(targets[0]) { + if len(targets) == 0 || !urlutil.IsRemoteURL(targets[0]) { return url, cmdContext, targets } cmdContext, targets = targets[0], targets[1:] diff --git a/commands/history/utils.go b/commands/history/utils.go index 90c892f11733..beab15a22a77 100644 --- a/commands/history/utils.go +++ b/commands/history/utils.go @@ -13,9 +13,9 @@ import ( "sync" "time" - "github.com/docker/buildx/build" "github.com/docker/buildx/builder" "github.com/docker/buildx/localstate" + "github.com/docker/buildx/util/urlutil" "github.com/docker/cli/cli/command" controlapi "github.com/moby/buildkit/api/services/control" "github.com/moby/buildkit/frontend/dockerfile/dfgitutil" @@ -56,7 +56,7 @@ func BuildName(fattrs map[string]string, ls *localstate.State) string { } var localPath string - if ls != nil && !build.IsRemoteURL(ls.LocalPath) { + if ls != nil && !urlutil.IsRemoteURL(ls.LocalPath) { if ls.LocalPath != "" && ls.LocalPath != "-" { localPath = filepath.ToSlash(ls.LocalPath) } diff --git a/util/urlutil/urlutil.go b/util/urlutil/urlutil.go new file mode 100644 index 000000000000..6996c10dbbf2 --- /dev/null +++ b/util/urlutil/urlutil.go @@ -0,0 +1,25 @@ +package urlutil + +import ( + "strings" + + "github.com/moby/buildkit/frontend/dockerfile/dfgitutil" +) + +// IsHTTPURL returns true if the provided str is an HTTP(S) URL by checking if +// it has a http:// or https:// scheme. No validation is performed to verify if +// the URL is well-formed. +func IsHTTPURL(str string) bool { + return strings.HasPrefix(str, "https://") || strings.HasPrefix(str, "http://") +} + +// IsRemoteURL returns true for HTTP(S) URLs and Git references. +func IsRemoteURL(c string) bool { + if IsHTTPURL(c) { + return true + } + if _, ok, _ := dfgitutil.ParseGitRef(c); ok { + return true + } + return false +} diff --git a/util/urlutil/urlutil_test.go b/util/urlutil/urlutil_test.go new file mode 100644 index 000000000000..a7a1b8b3f394 --- /dev/null +++ b/util/urlutil/urlutil_test.go @@ -0,0 +1,110 @@ +package urlutil + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestIsHTTPURL(t *testing.T) { + tests := []struct { + name string + input string + want bool + }{ + { + name: "https url", + input: "https://example.com/repo.git", + want: true, + }, + { + name: "http url", + input: "http://example.com/repo.git", + want: true, + }, + { + name: "http prefix only", + input: "http://", + want: true, + }, + { + name: "non-http protocol", + input: "git://example.com/repo.git", + want: false, + }, + { + name: "no protocol", + input: "example.com/repo.git", + want: false, + }, + { + name: "uppercase protocol is not matched", + input: "HTTPS://example.com/repo.git", + want: false, + }, + { + name: "leading whitespace does not match", + input: " https://example.com/repo.git", + want: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + require.Equal(t, tc.want, IsHTTPURL(tc.input)) + }) + } +} + +func TestIsRemoteURL(t *testing.T) { + tests := []struct { + name string + input string + want bool + }{ + { + name: "https url is remote", + input: "https://example.com/not-a-git-url", + want: true, + }, + { + name: "http url is remote", + input: "http://example.com/path", + want: true, + }, + { + name: "scp style git remote", + input: "git@github.com:moby/buildkit.git", + want: true, + }, + { + name: "github shorthand git remote", + input: "github.com/moby/buildkit", + want: true, + }, + { + name: "relative local path is not remote", + input: "./hack", + want: false, + }, + { + name: "plain local path is not remote", + input: "hack/dockerfiles", + want: false, + }, + { + name: "unknown protocol is not remote", + input: "docker-image://alpine", + want: false, + }, + { + name: "empty is not remote", + input: "", + want: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + require.Equal(t, tc.want, IsRemoteURL(tc.input)) + }) + } +}