Skip to content

Commit bfa57d5

Browse files
authored
Merge branch 'main' into renovate/actions-checkout-6.x
2 parents 94c77f7 + 3ce4349 commit bfa57d5

14 files changed

Lines changed: 1120 additions & 14 deletions

File tree

pkg/cache/filelock.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type FileLock interface {
2121
WithRLock(fn func() error) error
2222

2323
// TryWithRLock executes fn while holding a shared read lock when it can be
24-
// acquired immediately. It returns false, nil when another process holds an
24+
// acquired quickly. It returns false, nil when another process holds an
2525
// exclusive lock. On Windows, it executes without locking and returns true.
2626
TryWithRLock(fn func() error) (acquired bool, err error)
2727
}

pkg/cache/filelock_unix.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@ import (
1414

1515
const cacheLockTimeout = 2 * time.Second
1616

17+
// tryReadLockTimeout bounds the "best-effort, non-blocking" read-lock attempts
18+
// in WithRLock and TryWithRLock. It must stay well under cacheLockTimeout so
19+
// these still fail fast under genuine contention, but 1ms (the original
20+
// value) was tight enough that ordinary scheduling/IO jitter on a loaded CI
21+
// runner -- not actual lock contention -- could make an uncontended
22+
// acquisition miss its deadline, which TryWithRLock's callers (e.g. workdir
23+
// metadata reads) treat identically to "lock held by another process."
24+
// 50ms comfortably exceeds filelock's internal retryDelay (10ms), so a
25+
// briefly-held exclusive lock also has a real chance to clear within budget.
26+
const tryReadLockTimeout = 50 * time.Millisecond
27+
1728
type flockFileLock struct{ lockPath string }
1829

1930
// NewFileLock preserves the cache package API while using a stable sibling
@@ -50,8 +61,8 @@ func (l *flockFileLock) WithRLock(fn func() error) error {
5061
defer perf.Track(nil, "cache.flockFileLock.WithRLock")()
5162

5263
// Cache reads are deliberately best-effort. Preserve the old non-blocking
53-
// behavior by reading without a lock when one is immediately unavailable.
54-
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
64+
// behavior by reading without a lock when one isn't quickly available.
65+
ctx, cancel := context.WithTimeout(context.Background(), tryReadLockTimeout)
5566
defer cancel()
5667
err := filelock.New(l.lockPath).WithShared(ctx, fn)
5768
if errors.Is(err, filelock.ErrAcquire) && ctx.Err() != nil {
@@ -68,11 +79,11 @@ func cacheLockError(err error) error {
6879
}
6980

7081
// TryWithRLock executes fn only when a shared read lock can be acquired
71-
// immediately.
82+
// quickly (see tryReadLockTimeout).
7283
func (f *flockFileLock) TryWithRLock(fn func() error) (bool, error) {
7384
defer perf.Track(nil, "cache.flockFileLock.TryWithRLock")()
7485

75-
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
86+
ctx, cancel := context.WithTimeout(context.Background(), tryReadLockTimeout)
7687
defer cancel()
7788
err := filelock.New(f.lockPath).WithShared(ctx, fn)
7889
if errors.Is(err, filelock.ErrAcquire) {

pkg/provisioner/workdir/metadata_lock_unix_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ func TestWithMetadataFileLockUnix_LockUnavailable(t *testing.T) {
204204
// which succeeds against a directory). Instead, point the metadata file at a
205205
// path whose parent directory does not exist, so the open() call inside
206206
// TryWithRLock fails immediately with ENOENT (a hard error) rather than the
207-
// 1ms internal timeout merely expiring without an error - deterministically
207+
// internal try-lock timeout merely expiring without an error - deterministically
208208
// forcing the hard-error variant.
209209
func TestLoadMetadataWithReadLockUnix_LockUnavailable(t *testing.T) {
210210
tmpDir := t.TempDir()
@@ -221,8 +221,8 @@ func TestLoadMetadataWithReadLockUnix_LockUnavailable(t *testing.T) {
221221
// "!acquired && err == nil" branch (the "return nil, nil" contended-read
222222
// path). This is genuinely distinct from both LockUnavailable tests above:
223223
// here the lock file opens fine but is already held exclusively by another
224-
// holder, so TryWithRLock's 1ms internal timeout expires without an OS-level
225-
// error. The existing goroutine-based TestLoadMetadataWithReadLockUnix_ConcurrentReads
224+
// holder, so TryWithRLock's internal try-lock timeout expires without an
225+
// OS-level error. The existing goroutine-based TestLoadMetadataWithReadLockUnix_ConcurrentReads
226226
// doesn't reliably hit this branch because shared (read) locks don't
227227
// contend with each other, so this test forces it deterministically by
228228
// holding a separate exclusive lock, matching the style of

pkg/toolchain/installer/asset.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package installer
22

33
import (
44
"fmt"
5+
"path"
56
"path/filepath"
67
"runtime"
78
"strings"
@@ -42,6 +43,10 @@ func (i *Installer) buildAssetURLForPlatform(tool *registry.Tool, version, goos,
4243
return i.buildHTTPAssetURLForPlatform(tool, version, goos, goarch)
4344
case "github_release":
4445
return i.buildGitHubReleaseURLForPlatform(tool, version, goos, goarch)
46+
case "github_archive":
47+
return i.buildGitHubArchiveURL(tool, version)
48+
case "github_content":
49+
return i.buildGitHubContentURL(tool, version)
4550
default:
4651
return "", fmt.Errorf("%w: unsupported tool type: %s", ErrInvalidToolSpec, tool.Type)
4752
}
@@ -100,6 +105,61 @@ func (i *Installer) buildGitHubReleaseURLForPlatform(tool *registry.Tool, versio
100105
return url, nil
101106
}
102107

108+
// buildGitHubArchiveURL builds an asset URL for github_archive type tools.
109+
// Matches upstream aquaproj/aqua behavior: always uses GitHub's tag archive endpoint
110+
// with .tar.gz format. The Asset, URL, Format, and FormatOverrides fields are
111+
// intentionally ignored (Aqua's GetFormat() hardcodes "tar.gz" for github_archive).
112+
// See: https://github.com/aquaproj/aqua/blob/main/pkg/download/github_archive.go.
113+
func (i *Installer) buildGitHubArchiveURL(tool *registry.Tool, version string) (string, error) {
114+
defer perf.Track(nil, "Installer.buildGitHubArchiveURL")()
115+
116+
if tool.RepoOwner == "" || tool.RepoName == "" {
117+
return "", fmt.Errorf("%w: RepoOwner and RepoName must be set for github_archive type (got RepoOwner=%q, RepoName=%q)",
118+
ErrInvalidToolSpec, tool.RepoOwner, tool.RepoName)
119+
}
120+
121+
data := buildTemplateData(tool, version)
122+
return fmt.Sprintf("https://github.com/%s/%s/archive/refs/tags/%s.tar.gz",
123+
tool.RepoOwner, tool.RepoName, data.Version), nil
124+
}
125+
126+
// buildGitHubContentURL builds an asset URL for github_content type tools.
127+
// Matches upstream aquaproj/aqua behavior: downloads a single file from a repo
128+
// at a tag via raw.githubusercontent.com. The Asset, URL, Format, and
129+
// FormatOverrides fields are intentionally ignored for this type.
130+
// See: https://github.com/aquaproj/aqua/blob/main/pkg/download/github_content.go.
131+
func (i *Installer) buildGitHubContentURL(tool *registry.Tool, version string) (string, error) {
132+
defer perf.Track(nil, "Installer.buildGitHubContentURL")()
133+
134+
if err := validateGitHubContentFields(tool); err != nil {
135+
return "", err
136+
}
137+
data := buildTemplateData(tool, version)
138+
return formatGitHubContentURL(tool.RepoOwner, tool.RepoName, data.Version, tool.Path), nil
139+
}
140+
141+
// validateGitHubContentFields verifies that a github_content tool has the
142+
// required RepoOwner, RepoName, and Path fields. Pure function: no I/O, no state.
143+
func validateGitHubContentFields(tool *registry.Tool) error {
144+
if tool.RepoOwner == "" || tool.RepoName == "" || tool.Path == "" {
145+
return fmt.Errorf("%w: RepoOwner, RepoName, and Path must be set for github_content type (got RepoOwner=%q, RepoName=%q, Path=%q)",
146+
ErrInvalidToolSpec, tool.RepoOwner, tool.RepoName, tool.Path)
147+
}
148+
// Path is a repo-relative URL segment (always forward-slash, regardless of
149+
// host OS), so use the "path" package rather than "path/filepath" here.
150+
if path.IsAbs(tool.Path) || strings.Contains(tool.Path, "..") {
151+
return fmt.Errorf("%w: Path must be a relative repository path for github_content type (got Path=%q)",
152+
ErrInvalidToolSpec, tool.Path)
153+
}
154+
return nil
155+
}
156+
157+
// formatGitHubContentURL formats a raw.githubusercontent.com URL from its
158+
// component parts. Pure function: no inputs beyond the parameters.
159+
func formatGitHubContentURL(owner, repo, version, path string) string {
160+
return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/%s", owner, repo, version, path)
161+
}
162+
103163
// archiveExtensions contains known archive file extensions.
104164
var archiveExtensions = []string{
105165
".tar.gz", ".tgz", ".zip", ".gz",

0 commit comments

Comments
 (0)