Skip to content

Commit 6481fb8

Browse files
committed
refactor(version): env-overridable API URL, stateless fetch
Resolve the version API endpoint at call time, honoring ZEROPS_VERSION_API_URL so tests (and mirrors) can point it elsewhere without rebuilding. Drop the package-level sync.Once/latestResponse memoization: fetch() now relies on the on-disk cache for within-invocation dedup, which removes the global mutable state and the test-only reset hook it required.
1 parent 1271e1b commit 6481fb8

2 files changed

Lines changed: 33 additions & 29 deletions

File tree

src/constants/zerops.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const (
2828
CliZcliYamlFilePathEnvVar = "ZEROPS_CLI_YAML_FILE_PATH"
2929
CliTerminalMode = "ZEROPS_CLI_TERMINAL_MODE"
3030
CliTokenEnvVar = "ZEROPS_TOKEN"
31+
VersionApiUrlEnvVar = "ZEROPS_VERSION_API_URL"
3132
)
3233

3334
type pathReceiver func(fileMode os.FileMode) (path string, err error)

src/version/version.go

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,30 @@ import (
55
"encoding/json"
66
"fmt"
77
"net/http"
8+
"os"
89
"runtime"
9-
"sync"
1010
"time"
1111

1212
"github.com/pkg/errors"
1313
"golang.org/x/mod/semver"
1414

15+
"github.com/zeropsio/zcli/src/constants"
1516
"github.com/zeropsio/zcli/src/httpClient"
1617
"github.com/zeropsio/zcli/src/printer"
1718
)
1819

19-
const apiUrl = "https://api.app-prg1.zerops.io/api/rest/public/zcli/version"
20+
const defaultApiUrl = "https://api.app-prg1.zerops.io/api/rest/public/zcli/version"
2021

21-
var version = "local"
22+
// apiURL returns the version API endpoint, honoring an env override so tests
23+
// (and mirrors) can point it elsewhere without rebuilding.
24+
func apiURL() string {
25+
if u := os.Getenv(constants.VersionApiUrlEnvVar); u != "" {
26+
return u
27+
}
28+
return defaultApiUrl
29+
}
2230

23-
var (
24-
fetchOnce sync.Once
25-
latestResponse *apiResponse
26-
errFetch error
27-
)
31+
var version = "local"
2832

2933
func GetCurrent() string {
3034
return version
@@ -125,36 +129,35 @@ func isUpdateAvailable(current, latest string) bool {
125129
return semver.Compare(current, latest) < 0
126130
}
127131

132+
// fetch returns the latest-release response, preferring the on-disk cache.
133+
// The cache also dedupes within a single invocation: the first call writes it
134+
// and any later call (e.g. GetLatestUrl after GetLatest) reads it back, so
135+
// there's no need for in-process memoization.
128136
func fetch(ctx context.Context) (*apiResponse, error) {
129-
fetchOnce.Do(func() {
130-
if entry, err := loadCacheEntry(); err == nil && entry != nil && entry.Fresh() {
131-
latestResponse = entry.Response
132-
return
133-
}
134-
resp, err := fetchFromNetwork(ctx)
135-
if err != nil {
136-
// Stale cache beats no answer at all.
137-
if entry, cacheErr := loadCacheEntry(); cacheErr == nil && entry != nil {
138-
latestResponse = entry.Response
139-
return
140-
}
141-
errFetch = err
142-
return
137+
if entry, err := loadCacheEntry(); err == nil && entry != nil && entry.Fresh() {
138+
return entry.Response, nil
139+
}
140+
resp, err := fetchFromNetwork(ctx)
141+
if err != nil {
142+
// Stale cache beats no answer at all.
143+
if entry, cacheErr := loadCacheEntry(); cacheErr == nil && entry != nil {
144+
return entry.Response, nil
143145
}
144-
latestResponse = resp
145-
_ = writeCacheEntry(resp)
146-
})
147-
return latestResponse, errFetch
146+
return nil, err
147+
}
148+
_ = writeCacheEntry(resp)
149+
return resp, nil
148150
}
149151

150152
func fetchFromNetwork(ctx context.Context) (*apiResponse, error) {
153+
url := apiURL()
151154
client := httpClient.New(ctx, httpClient.Config{HttpTimeout: time.Second * 5})
152-
resp, err := client.Get(ctx, apiUrl)
155+
resp, err := client.Get(ctx, url)
153156
if err != nil {
154-
return nil, errors.Wrapf(err, "version api request to %s failed", apiUrl)
157+
return nil, errors.Wrapf(err, "version api request to %s failed", url)
155158
}
156159
if resp.StatusCode != http.StatusOK {
157-
return nil, errors.Errorf("version api %s returned status %d", apiUrl, resp.StatusCode)
160+
return nil, errors.Errorf("version api %s returned status %d", url, resp.StatusCode)
158161
}
159162
out := &apiResponse{}
160163
if err := json.Unmarshal(resp.Body, out); err != nil {

0 commit comments

Comments
 (0)