diff --git a/frontend/dockerfile/dfgitutil/git_ref.go b/frontend/dockerfile/dfgitutil/git_ref.go index f1cb45f23502..a45c9fb7d5bc 100644 --- a/frontend/dockerfile/dfgitutil/git_ref.go +++ b/frontend/dockerfile/dfgitutil/git_ref.go @@ -212,3 +212,17 @@ func (gf *GitRef) loadQuery(query url.Values) error { } return nil } + +// FragmentFormat returns a simplified git URL in fragment format with only ref. +// If the URL cannot be parsed, the original string is returned with false. +func FragmentFormat(remote string) (string, bool) { + gitRef, _, err := ParseGitRef(remote) + if err != nil || gitRef == nil { + return remote, false + } + u := gitRef.Remote + if gitRef.Ref != "" { + u += "#" + gitRef.Ref + } + return u, true +} diff --git a/frontend/dockerfile/dfgitutil/git_ref_test.go b/frontend/dockerfile/dfgitutil/git_ref_test.go index a391e81168e7..c767e5f1c7b2 100644 --- a/frontend/dockerfile/dfgitutil/git_ref_test.go +++ b/frontend/dockerfile/dfgitutil/git_ref_test.go @@ -235,3 +235,174 @@ func TestParseGitRef(t *testing.T) { }) } } + +func TestFragmentFormat(t *testing.T) { + cases := []struct { + ref string + expected string + ok bool + }{ + { + ref: "https://example.com/", + expected: "https://example.com/", + ok: false, + }, + { + ref: "https://example.com/foo.git", + expected: "https://example.com/foo.git", + ok: true, + }, + { + ref: "https://example.com/foo.git#deadbeef", + expected: "https://example.com/foo.git#deadbeef", + ok: true, + }, + { + ref: "https://example.com/foo.git#release/1.2", + expected: "https://example.com/foo.git#release/1.2", + ok: true, + }, + { + ref: "https://example.com/foo.git/", + expected: "https://example.com/foo.git/", + ok: false, + }, + { + ref: "https://example.com/foo.git.bar", + expected: "https://example.com/foo.git.bar", + ok: false, + }, + { + ref: "git://example.com/foo", + expected: "git://example.com/foo", + ok: true, + }, + { + ref: "github.com/moby/buildkit", + expected: "github.com/moby/buildkit", + ok: true, + }, + { + ref: "github.com/moby/buildkit#master", + expected: "github.com/moby/buildkit#master", + ok: true, + }, + { + ref: "custom.xyz/moby/buildkit.git", + expected: "custom.xyz/moby/buildkit.git", + ok: false, + }, + { + ref: "https://github.com/moby/buildkit", + expected: "https://github.com/moby/buildkit", + ok: false, + }, + { + ref: "https://github.com/moby/buildkit.git", + expected: "https://github.com/moby/buildkit.git", + ok: true, + }, + { + ref: "https://foo:bar@github.com/moby/buildkit.git", + expected: "https://foo:bar@github.com/moby/buildkit.git", + ok: true, + }, + { + ref: "git@github.com:moby/buildkit", + expected: "git@github.com:moby/buildkit", + ok: true, + }, + { + ref: "git@github.com:moby/buildkit.git", + expected: "git@github.com:moby/buildkit.git", + ok: true, + }, + { + ref: "git@bitbucket.org:atlassianlabs/atlassian-docker.git", + expected: "git@bitbucket.org:atlassianlabs/atlassian-docker.git", + ok: true, + }, + { + ref: "https://github.com/foo/bar.git#baz/qux:quux/quuz", + expected: "https://github.com/foo/bar.git#baz/qux", + ok: true, + }, + { + ref: "http://github.com/docker/docker.git:#branch", + expected: "http://github.com/docker/docker.git:#branch", + ok: false, + }, + { + ref: "https://github.com/docker/docker.git#:myfolder", + expected: "https://github.com/docker/docker.git", + ok: true, + }, + { + ref: "./.git", + expected: "./.git", + ok: false, + }, + { + ref: ".git", + expected: ".git", + ok: false, + }, + { + ref: "https://github.com/docker/docker.git?ref=v1.0.0&subdir=/subdir", + expected: "https://github.com/docker/docker.git#v1.0.0", + ok: true, + }, + { + ref: "https://github.com/moby/buildkit.git?subdir=/subdir#v1.0.0", + expected: "https://github.com/moby/buildkit.git#v1.0.0", + ok: true, + }, + { + ref: "https://github.com/moby/buildkit.git?tag=v1.0.0", + expected: "https://github.com/moby/buildkit.git#refs/tags/v1.0.0", + ok: true, + }, + { + ref: "github.com/moby/buildkit?tag=v1.0.0", + expected: "github.com/moby/buildkit#refs/tags/v1.0.0", + ok: true, + }, + { + ref: "https://github.com/moby/buildkit.git?branch=v1.0", + expected: "https://github.com/moby/buildkit.git#refs/heads/v1.0", + ok: true, + }, + { + ref: "https://github.com/moby/buildkit.git?ref=v1.0.0#v1.2.3", + expected: "https://github.com/moby/buildkit.git?ref=v1.0.0#v1.2.3", + ok: false, + }, + { + ref: "https://github.com/moby/buildkit.git?ref=v1.0.0&tag=v1.2.3", + expected: "https://github.com/moby/buildkit.git?ref=v1.0.0&tag=v1.2.3", + ok: false, + }, + { + ref: "https://github.com/moby/buildkit.git?tag=v1.0.0&branch=v1.0", + expected: "https://github.com/moby/buildkit.git?tag=v1.0.0&branch=v1.0", + ok: false, + }, + { + ref: "git@github.com:moby/buildkit.git?subdir=/subdir#v1.0.0", + expected: "git@github.com:moby/buildkit.git#v1.0.0", + ok: true, + }, + { + ref: "https://github.com/moby/buildkit.git?invalid=123", + expected: "https://github.com/moby/buildkit.git?invalid=123", + ok: false, + }, + } + for i, tt := range cases { + t.Run(fmt.Sprintf("case%d", i+1), func(t *testing.T) { + got, ok := FragmentFormat(tt.ref) + require.Equal(t, tt.expected, got) + require.Equal(t, tt.ok, ok) + }) + } +} diff --git a/solver/llbsolver/provenance/predicate.go b/solver/llbsolver/provenance/predicate.go index bf01c0b9d6cc..554677adf874 100644 --- a/solver/llbsolver/provenance/predicate.go +++ b/solver/llbsolver/provenance/predicate.go @@ -7,8 +7,8 @@ import ( "github.com/containerd/platforms" slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" + "github.com/moby/buildkit/frontend/dockerfile/dfgitutil" provenancetypes "github.com/moby/buildkit/solver/llbsolver/provenance/types" - "github.com/moby/buildkit/util/gitutil" "github.com/moby/buildkit/util/purl" "github.com/moby/buildkit/util/urlutil" "github.com/package-url/packageurl-go" @@ -70,35 +70,7 @@ func digestSetForCommit(commit string) slsa.DigestSet { } func findMaterial(srcs provenancetypes.Sources, uri string) (*slsa.ProvenanceMaterial, bool) { - // Git URLs in querystring format or subdir need to be converted to fragment format with only ref - gitRef, err := gitutil.ParseURL(uri) - if err == nil && gitRef != nil { - u := gitRef.Remote - var ref string - if gitRef.Opts != nil { - ref = gitRef.Opts.Ref - } - if len(gitRef.Query) > 0 { - for k, v := range gitRef.Query { - if len(v) == 0 { - continue - } - switch k { - case "ref": - ref = v[0] - case "branch": - ref = "refs/heads/" + v[0] - case "tag": - ref = "refs/tags/" + v[0] - } - } - } - if ref != "" { - u += "#" + ref - } - uri = u - } - + uri, _ = dfgitutil.FragmentFormat(uri) for _, s := range srcs.Git { if s.URL == uri { return &slsa.ProvenanceMaterial{ diff --git a/util/gitutil/git_url.go b/util/gitutil/git_url.go index 80717a67a660..f06f0e438a8c 100644 --- a/util/gitutil/git_url.go +++ b/util/gitutil/git_url.go @@ -30,7 +30,7 @@ var supportedProtos = map[string]struct{}{ var protoRegexp = regexp.MustCompile(`^[a-zA-Z0-9]+://`) -// URL is a custom URL type that points to a remote Git repository. +// GitURL is a custom URL type that points to a remote Git repository. // // URLs can be parsed from both standard URLs (e.g. // "https://github.com/moby/buildkit.git"), as well as SCP-like URLs (e.g.