From 08b97d2dd4435ee10f199029ebe95f1689aa9828 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 15 Jul 2024 13:46:40 +0200 Subject: [PATCH 1/7] gha: update actions/checkout@v4 Signed-off-by: Sebastiaan van Stijn --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/fossa.yml | 2 +- .github/workflows/test.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 76bf31e..318d617 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -34,7 +34,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 2 - diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml index 9b4419d..919944b 100644 --- a/.github/workflows/fossa.yml +++ b/.github/workflows/fossa.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run FOSSA scan and upload build data uses: fossa-contrib/fossa-action@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1382d34..b8773bc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: runs-on: ${{ matrix.os }} timeout-minutes: 10 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v4 From 5291d101b9b50b2d108a8a1b58e86d5e1e5f5d43 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 15 Jul 2024 13:47:33 +0200 Subject: [PATCH 2/7] gha: update actions/setup-go@v5 Signed-off-by: Sebastiaan van Stijn --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b8773bc..7578d8a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} From 92b1472c5243c7a953306852fb6c150407a6c5ab Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 15 Jul 2024 13:49:41 +0200 Subject: [PATCH 3/7] gha: update golangci/golangci-lint-action@v6 Signed-off-by: Sebastiaan van Stijn --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7578d8a..366182d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,7 @@ jobs: make build - name: lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v6 with: version: v1.53 args: --print-resources-usage --timeout=10m --verbose From 2ab2cce5b16d401d4819dd5c1c15ff6249f07303 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 15 Jul 2024 13:50:00 +0200 Subject: [PATCH 4/7] gha: update codecov/codecov-action@v4 Signed-off-by: Sebastiaan van Stijn --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 366182d..dea30c7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,6 +44,6 @@ jobs: make coverage - name: Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: directory: ./ From 0c5e42c50c5503a4a5b503f85e15e625d6aedf51 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 15 Jul 2024 13:51:12 +0200 Subject: [PATCH 5/7] gha: update golangci-lint to v1.59 Error: fuzz_test.go:11:14: unused-parameter: parameter 't' seems to be unused, consider removing or renaming it as _ (revive) f.Fuzz(func(t *testing.T, data string) { ^ Signed-off-by: Sebastiaan van Stijn --- .github/workflows/test.yml | 2 +- fuzz_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dea30c7..6a78e20 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,7 +36,7 @@ jobs: - name: lint uses: golangci/golangci-lint-action@v6 with: - version: v1.53 + version: v1.59 args: --print-resources-usage --timeout=10m --verbose - name: Test diff --git a/fuzz_test.go b/fuzz_test.go index c64c54e..1aaebba 100644 --- a/fuzz_test.go +++ b/fuzz_test.go @@ -8,7 +8,7 @@ import ( // that targets ParseNormalizedNamed // nolint:deadcode func FuzzParseNormalizedNamed(f *testing.F) { - f.Fuzz(func(t *testing.T, data string) { + f.Fuzz(func(_ *testing.T, data string) { _, _ = ParseNormalizedNamed(data) }) } From 343e590ea7367ce1e923766796930ea9dfdca40e Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 15 Jul 2024 13:40:07 +0200 Subject: [PATCH 6/7] gha: update to go1.21.x, go1.22.x Signed-off-by: Sebastiaan van Stijn --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6a78e20..3d345e9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: build: strategy: matrix: - go-version: [1.20.x, 1.21.x] + go-version: [1.21.x, 1.22.x] os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} timeout-minutes: 10 From 4ca14034c40611bc3536fc703ffa6a22fb8e6659 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Mon, 15 Jul 2024 13:39:09 +0200 Subject: [PATCH 7/7] use sync.OnceValue for various regular expressions, require go1.21 Using regex.MustCompile consumes a significant amount of memory when importing the package, even if those regular expressions are not used. This changes compiling the regular expressions to use a sync.OnceValue so that they're only compiled the first time they're used. There are various regular expressions remaining that are still compiled on import, but these are exported, so changing them to a sync.OnceValue would be a breaking change; we can still decide to do so, but leaving that for a follow-up. It's worth noting that sync.OnceValue requires go1.21 or up, so raising the minimum version accordingly. Signed-off-by: Sebastiaan van Stijn --- go.mod | 2 +- normalize.go | 4 ++-- reference.go | 10 +++++----- regexp.go | 17 +++++++++++++---- regexp_test.go | 8 ++++---- 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 25cf64a..e82b093 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/distribution/reference -go 1.20 +go 1.21 require github.com/opencontainers/go-digest v1.0.0 diff --git a/normalize.go b/normalize.go index f412831..9e081d7 100644 --- a/normalize.go +++ b/normalize.go @@ -54,7 +54,7 @@ type normalizedNamed interface { // qualified reference. If the value may be an identifier // use ParseAnyReference. func ParseNormalizedNamed(s string) (Named, error) { - if ok := anchoredIdentifierRegexp.MatchString(s); ok { + if ok := anchoredIdentifierRegexp().MatchString(s); ok { return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s) } domain, remainder := splitDockerDomain(s) @@ -244,7 +244,7 @@ func TagNameOnly(ref Named) Named { // ParseAnyReference parses a reference string as a possible identifier, // full digest, or familiar name. func ParseAnyReference(ref string) (Reference, error) { - if ok := anchoredIdentifierRegexp.MatchString(ref); ok { + if ok := anchoredIdentifierRegexp().MatchString(ref); ok { return digestReference("sha256:" + ref), nil } if dgst, err := digest.Parse(ref); err == nil { diff --git a/reference.go b/reference.go index 900398b..ec3a22a 100644 --- a/reference.go +++ b/reference.go @@ -174,7 +174,7 @@ func Path(named Named) (name string) { // If no valid hostname is found, the hostname is empty and the full value // is returned as name func splitDomain(name string) (string, string) { - match := anchoredNameRegexp.FindStringSubmatch(name) + match := anchoredNameRegexp().FindStringSubmatch(name) if len(match) != 3 { return "", name } @@ -197,7 +197,7 @@ func Parse(s string) (Reference, error) { var repo repository - nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1]) + nameMatch := anchoredNameRegexp().FindStringSubmatch(matches[1]) if len(nameMatch) == 3 { repo.domain = nameMatch[1] repo.path = nameMatch[2] @@ -248,7 +248,7 @@ func ParseNamed(s string) (Named, error) { // WithName returns a named object representing the given string. If the input // is invalid ErrReferenceInvalidFormat will be returned. func WithName(name string) (Named, error) { - match := anchoredNameRegexp.FindStringSubmatch(name) + match := anchoredNameRegexp().FindStringSubmatch(name) if match == nil || len(match) != 3 { return nil, ErrReferenceInvalidFormat } @@ -266,7 +266,7 @@ func WithName(name string) (Named, error) { // WithTag combines the name from "name" and the tag from "tag" to form a // reference incorporating both the name and the tag. func WithTag(name Named, tag string) (NamedTagged, error) { - if !anchoredTagRegexp.MatchString(tag) { + if !anchoredTagRegexp().MatchString(tag) { return nil, ErrTagInvalidFormat } var repo repository @@ -292,7 +292,7 @@ func WithTag(name Named, tag string) (NamedTagged, error) { // WithDigest combines the name from "name" and the digest from "digest" to form // a reference incorporating both the name and the digest. func WithDigest(name Named, digest digest.Digest) (Canonical, error) { - if !anchoredDigestRegexp.MatchString(digest.String()) { + if !anchoredDigestRegexp().MatchString(digest.String()) { return nil, ErrDigestInvalidFormat } var repo repository diff --git a/regexp.go b/regexp.go index 65bc49d..3d65c39 100644 --- a/regexp.go +++ b/regexp.go @@ -3,6 +3,7 @@ package reference import ( "regexp" "strings" + "sync" ) // DigestRegexp matches well-formed digests, including algorithm (e.g. "sha256:"). @@ -111,11 +112,15 @@ var ( // anchoredTagRegexp matches valid tag names, anchored at the start and // end of the matched string. - anchoredTagRegexp = regexp.MustCompile(anchored(tag)) + anchoredTagRegexp = sync.OnceValue(func() *regexp.Regexp { + return regexp.MustCompile(anchored(tag)) + }) // anchoredDigestRegexp matches valid digests, anchored at the start and // end of the matched string. - anchoredDigestRegexp = regexp.MustCompile(anchored(digestPat)) + anchoredDigestRegexp = sync.OnceValue(func() *regexp.Regexp { + return regexp.MustCompile(anchored(digestPat)) + }) // pathComponent restricts path-components to start with an alphanumeric // character, with following parts able to be separated by a separator @@ -131,13 +136,17 @@ var ( // anchoredNameRegexp is used to parse a name value, capturing the // domain and trailing components. - anchoredNameRegexp = regexp.MustCompile(anchored(optional(capture(domainAndPort), `/`), capture(remoteName))) + anchoredNameRegexp = sync.OnceValue(func() *regexp.Regexp { + return regexp.MustCompile(anchored(optional(capture(domainAndPort), `/`), capture(remoteName))) + }) referencePat = anchored(capture(namePat), optional(`:`, capture(tag)), optional(`@`, capture(digestPat))) // anchoredIdentifierRegexp is used to check or match an // identifier value, anchored at start and end of string. - anchoredIdentifierRegexp = regexp.MustCompile(anchored(identifier)) + anchoredIdentifierRegexp = sync.OnceValue(func() *regexp.Regexp { + return regexp.MustCompile(anchored(identifier)) + }) ) // optional wraps the expression in a non-capturing group and makes the diff --git a/regexp_test.go b/regexp_test.go index ca4680d..b92ed6a 100644 --- a/regexp_test.go +++ b/regexp_test.go @@ -176,9 +176,9 @@ func TestDomainRegexp(t *testing.T) { func TestFullNameRegexp(t *testing.T) { t.Parallel() - if anchoredNameRegexp.NumSubexp() != 2 { + if anchoredNameRegexp().NumSubexp() != 2 { t.Fatalf("anchored name regexp should have two submatches: %v, %v != 2", - anchoredNameRegexp, anchoredNameRegexp.NumSubexp()) + anchoredNameRegexp(), anchoredNameRegexp().NumSubexp()) } tests := []regexpMatch{ @@ -469,7 +469,7 @@ func TestFullNameRegexp(t *testing.T) { tc := tc t.Run(tc.input, func(t *testing.T) { t.Parallel() - checkRegexp(t, anchoredNameRegexp, tc) + checkRegexp(t, anchoredNameRegexp(), tc) }) } } @@ -580,7 +580,7 @@ func TestIdentifierRegexp(t *testing.T) { tc := tc t.Run(tc.input, func(t *testing.T) { t.Parallel() - match := anchoredIdentifierRegexp.MatchString(tc.input) + match := anchoredIdentifierRegexp().MatchString(tc.input) if match != tc.match { t.Errorf("Expected match=%t, got %t", tc.match, match) }