Skip to content

Commit ef5a1dc

Browse files
authored
Add download cache for version dependency files (#2974)
* Add download cache for version files to reduce network calls Implements a caching mechanism for version dependency files (latest.json and min_cli_version.json) to avoid repeated network requests. The cache expires after 3 hours and is stored in a dedicated download-cache directory. This improves CLI performance by reducing unnecessary downloads while ensuring version information stays reasonably up-to-date. * Fix unit tests to mock DownloadWithCache method
1 parent 758cf10 commit ef5a1dc

File tree

18 files changed

+128
-42
lines changed

18 files changed

+128
-42
lines changed

cmd/root.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,13 @@ func setupEnv() (string, error) {
328328
return "", err
329329
}
330330

331+
// Create download cache dir if it doesn't exist
332+
downloadCacheDir := filepath.Join(baseDir, constants.DownloadCacheDir)
333+
if err = os.MkdirAll(downloadCacheDir, os.ModePerm); err != nil {
334+
fmt.Printf("failed creating the download cache dir %s: %s\n", downloadCacheDir, err)
335+
return "", err
336+
}
337+
331338
return baseDir, nil
332339
}
333340

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ require (
3838
github.com/spf13/pflag v1.0.7
3939
github.com/spf13/viper v1.20.1
4040
github.com/stretchr/testify v1.10.0
41+
go.uber.org/mock v0.5.2
4142
go.uber.org/zap v1.27.0
4243
golang.org/x/crypto v0.39.0
4344
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
@@ -225,7 +226,6 @@ require (
225226
go.opentelemetry.io/otel/trace v1.36.0 // indirect
226227
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
227228
go.uber.org/automaxprocs v1.6.0 // indirect
228-
go.uber.org/mock v0.5.2 // indirect
229229
go.uber.org/multierr v1.11.0 // indirect
230230
golang.org/x/net v0.41.0 // indirect
231231
golang.org/x/sys v0.33.0 // indirect

internal/mocks/binary_checker.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/mocks/downloader.go

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/mocks/installer.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/mocks/plugin_binary_downloader.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/mocks/publisher.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/application/downloader.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const (
3838
type Downloader interface {
3939
Download(url string) ([]byte, error)
4040
DownloadWithTee(url string, path string) ([]byte, error)
41+
DownloadWithCache(url string, path string, duration time.Duration) ([]byte, error)
4142
GetLatestReleaseVersion(org, repo, component string) (string, error)
4243
GetLatestPreReleaseVersion(org, repo, component string) (string, error)
4344
GetAllReleasesForRepo(org, repo, component string, kind ReleaseKind) ([]string, error)
@@ -74,6 +75,38 @@ func (d downloader) DownloadWithTee(url string, path string) ([]byte, error) {
7475
return bs, os.WriteFile(path, bs, constants.WriteReadReadPerms)
7576
}
7677

78+
func (d downloader) DownloadWithCache(url string, path string, duration time.Duration) ([]byte, error) {
79+
var data []byte
80+
var useCache bool
81+
82+
// Check if cache file exists and is recent
83+
if fileInfo, err := os.Stat(path); err == nil {
84+
if time.Since(fileInfo.ModTime()) < duration {
85+
// Cache is valid, read from it
86+
data, err = os.ReadFile(path)
87+
if err == nil {
88+
useCache = true
89+
}
90+
}
91+
}
92+
93+
// If cache is not valid or doesn't exist, download
94+
if !useCache {
95+
var err error
96+
data, err = d.Download(url)
97+
if err != nil {
98+
return nil, err
99+
}
100+
101+
// Save to cache
102+
if err := os.MkdirAll(filepath.Dir(path), constants.DefaultPerms755); err == nil {
103+
_ = os.WriteFile(path, data, constants.WriteReadReadPerms)
104+
}
105+
}
106+
107+
return data, nil
108+
}
109+
77110
// GetLatestPreReleaseVersion returns the latest available pre release or release version from github
78111
func (d downloader) GetLatestPreReleaseVersion(org, repo, component string) (string, error) {
79112
releases, err := d.GetAllReleasesForRepo(org, repo, component, All)

pkg/constants/constants.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const (
3030
AvalancheCliBinDir = "bin"
3131
RunDir = "runs"
3232
ServicesDir = "services"
33+
DownloadCacheDir = "download-cache"
3334

3435
SuffixSeparator = "_"
3536
SidecarFileName = "sidecar.json"
@@ -71,6 +72,8 @@ const (
7172

7273
EVMEventLookupTimeout = 2 * time.Minute
7374

75+
DownloadCacheExpiration = 3 * time.Hour
76+
7477
SSHServerStartTimeout = 1 * time.Minute
7578
SSHScriptTimeout = 2 * time.Minute
7679
SSHLongRunningScriptTimeout = 10 * time.Minute
@@ -303,8 +306,10 @@ const (
303306
FujiAvalancheGoV113 = "v1.13.0-fuji"
304307
AvalancheGoCompatibilityURL = "https://raw.githubusercontent.com/ava-labs/avalanchego/master/version/compatibility.json"
305308
SubnetEVMRPCCompatibilityURL = "https://raw.githubusercontent.com/ava-labs/subnet-evm/master/compatibility.json"
306-
CLILatestDependencyURL = "https://raw.githubusercontent.com/ava-labs/avalanche-cli/main/versions/latest.json"
307-
CLIMinVersionURL = "https://raw.githubusercontent.com/ava-labs/avalanche-cli/main/versions/min_cli_version.json"
309+
CLILatestDependencyFileName = "latest.json"
310+
CLIMinVersionFileName = "min_cli_version.json"
311+
CLILatestDependencyURL = "https://raw.githubusercontent.com/ava-labs/avalanche-cli/main/versions/" + CLILatestDependencyFileName
312+
CLIMinVersionURL = "https://raw.githubusercontent.com/ava-labs/avalanche-cli/main/versions/" + CLIMinVersionFileName
308313

309314
YesLabel = "Yes"
310315
NoLabel = "No"

pkg/dependencies/dependencies.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"errors"
88
"fmt"
9+
"path/filepath"
910
"strconv"
1011

1112
"github.com/ava-labs/avalanche-cli/pkg/application"
@@ -28,13 +29,15 @@ func GetLatestAvalancheGoByProtocolVersion(app *application.Avalanche, rpcVersio
2829
}
2930

3031
func GetLatestCLISupportedDependencyVersion(app *application.Avalanche, dependencyName string, network models.Network, rpcVersion *int) (string, error) {
31-
dependencyBytes, err := app.Downloader.Download(constants.CLILatestDependencyURL)
32+
cacheDir := filepath.Join(app.GetBaseDir(), constants.DownloadCacheDir)
33+
cacheFile := filepath.Join(cacheDir, constants.CLILatestDependencyFileName)
34+
dependencyBytes, err := app.Downloader.DownloadWithCache(constants.CLILatestDependencyURL, cacheFile, constants.DownloadCacheExpiration)
3235
if err != nil {
3336
return "", err
3437
}
3538

3639
var parsedDependency models.CLIDependencyMap
37-
if err = json.Unmarshal(dependencyBytes, &parsedDependency); err != nil {
40+
if err := json.Unmarshal(dependencyBytes, &parsedDependency); err != nil {
3841
return "", err
3942
}
4043

0 commit comments

Comments
 (0)