Skip to content

Commit 495860d

Browse files
authored
Merge pull request #3720 from ActiveState/miked/CP-1054
Allow configuration of update URL
2 parents 1a88547 + 6688d6c commit 495860d

5 files changed

Lines changed: 92 additions & 15 deletions

File tree

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,4 +544,4 @@ jobs:
544544
session-build-macos-13
545545
session-build-macos-latest
546546
session-build-windows-2025
547-
session-build-ubuntu-24.04-arm
547+
session-build-ubuntu-24.04-arm

internal/constants/constants.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,9 @@ const SecurityPromptConfig = "security.prompt.enabled"
412412
// SecurityPromptLevelConfig is the config key used to determine the level of security prompts
413413
const SecurityPromptLevelConfig = "security.prompt.level"
414414

415+
// UpdateEndpointConfig is the config key used to determine the update endpoint to use
416+
const UpdateEndpointConfig = "update.endpoint"
417+
415418
// APIHostConfig is the config key used to determine the api host
416419
const APIHostConfig = "api.host"
417420

internal/testhelpers/e2e/session.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ type Session struct {
6464
ExecutorExe string
6565
spawned []*SpawnedCmd
6666
ignoreLogErrors bool
67+
cfg *config.Instance
6768
cache keyCache
6869
cfg *config.Instance
6970
}
@@ -200,6 +201,7 @@ func new(t *testing.T, retainDirs, updatePath bool, extraEnv ...string) *Session
200201

201202
cfg, err := config.NewCustom(dirs.Config, singlethread.New(), true)
202203
require.NoError(session.T, err)
204+
session.cfg = cfg
203205

204206
if err := cfg.Set(constants.SecurityPromptConfig, false); err != nil {
205207
require.NoError(session.T, err)

internal/updater/checker.go

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818
"github.com/ActiveState/cli/internal/logging"
1919
"github.com/ActiveState/cli/internal/retryhttp"
2020
"github.com/ActiveState/cli/internal/rtutils/ptr"
21+
22+
configMediator "github.com/ActiveState/cli/internal/mediators/config"
2123
)
2224

2325
type Configurable interface {
@@ -32,30 +34,28 @@ var (
3234
InvocationSourceUpdate InvocationSource = "update"
3335
)
3436

37+
func init() {
38+
configMediator.RegisterOption(constants.UpdateEndpointConfig, configMediator.String, "")
39+
}
40+
3541
type Checker struct {
36-
cfg Configurable
37-
an analytics.Dispatcher
38-
apiInfoURL string
39-
retryhttp *retryhttp.Client
40-
cache *AvailableUpdate
41-
done chan struct{}
42+
cfg Configurable
43+
an analytics.Dispatcher
44+
retryhttp *retryhttp.Client
45+
cache *AvailableUpdate
46+
done chan struct{}
4247

4348
InvocationSource InvocationSource
4449
}
4550

4651
func NewDefaultChecker(cfg Configurable, an analytics.Dispatcher) *Checker {
47-
infoURL := constants.APIUpdateInfoURL
48-
if url, ok := os.LookupEnv("_TEST_UPDATE_INFO_URL"); ok {
49-
infoURL = url
50-
}
51-
return NewChecker(cfg, an, infoURL, retryhttp.DefaultClient)
52+
return NewChecker(cfg, an, retryhttp.DefaultClient)
5253
}
5354

54-
func NewChecker(cfg Configurable, an analytics.Dispatcher, infoURL string, httpget *retryhttp.Client) *Checker {
55+
func NewChecker(cfg Configurable, an analytics.Dispatcher, httpget *retryhttp.Client) *Checker {
5556
return &Checker{
5657
cfg,
5758
an,
58-
infoURL,
5959
httpget,
6060
nil,
6161
make(chan struct{}),
@@ -83,11 +83,26 @@ func (u *Checker) infoURL(tag, desiredVersion, branchName, platform, arch string
8383
v.Set("target-version", desiredVersion)
8484
}
8585

86+
var (
87+
infoURL string
88+
89+
envUrl = os.Getenv("_TEST_UPDATE_INFO_URL")
90+
cfgUrl = u.cfg.GetString(constants.UpdateEndpointConfig)
91+
)
92+
switch {
93+
case envUrl != "":
94+
infoURL = envUrl
95+
case cfgUrl != "":
96+
infoURL = cfgUrl
97+
default:
98+
infoURL = constants.APIUpdateInfoURL
99+
}
100+
86101
if tag != "" {
87102
v.Set("tag", tag)
88103
}
89104

90-
return u.apiInfoURL + "/info?" + v.Encode()
105+
return infoURL + "/info?" + v.Encode()
91106
}
92107

93108
func (u *Checker) getUpdateInfo(desiredChannel, desiredVersion string) (*AvailableUpdate, error) {
@@ -118,13 +133,15 @@ func (u *Checker) getUpdateInfo(desiredChannel, desiredVersion string) (*Availab
118133
logging.Debug("Update info 404s: %v", errs.JoinMessage(err))
119134
label = anaConst.UpdateLabelUnavailable
120135
msg = anaConst.UpdateErrorNotFound
136+
info = &AvailableUpdate{}
121137

122138
// The request could not be satisfied or service is unavailable. This happens when Cloudflare
123139
// blocks access, or the service is unavailable in a particular geographic location.
124140
case resp.StatusCode == 403 || resp.StatusCode == 503:
125141
logging.Warning("Update info request blocked or service unavailable: %v", err)
126142
label = anaConst.UpdateLabelUnavailable
127143
msg = anaConst.UpdateErrorBlocked
144+
info = &AvailableUpdate{}
128145

129146
// If all went well.
130147
default:

test/integration/update_int_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,61 @@ func (suite *UpdateIntegrationTestSuite) TestUpdateTags() {
275275
}
276276
}
277277

278+
func (suite *UpdateIntegrationTestSuite) TestUpdateHost_SetBeforeInvocation() {
279+
suite.OnlyRunForTags(tagsuite.Update)
280+
281+
ts := e2e.New(suite.T(), false)
282+
defer ts.Close()
283+
284+
ts.SetConfig(constants.UpdateEndpointConfig, "https://test.example.com/update")
285+
suite.Assert().Equal(ts.GetConfig(constants.UpdateEndpointConfig), "https://test.example.com/update")
286+
287+
cp := ts.SpawnWithOpts(
288+
e2e.OptArgs("--version"),
289+
)
290+
cp.ExpectExitCode(0)
291+
292+
correctHostCount := 0
293+
incorrectHostCount := 0
294+
for _, path := range ts.LogFiles() {
295+
contents := string(fileutils.ReadFileUnsafe(path))
296+
if strings.Contains(contents, "https://test.example.com/update") {
297+
correctHostCount++
298+
}
299+
if strings.Contains(contents, "https://platform.activestate.com/update") {
300+
incorrectHostCount++
301+
}
302+
}
303+
suite.Assert().Greater(correctHostCount, 0, "Log file should contain the configured API host 'test.example.com'")
304+
suite.Assert().Equal(incorrectHostCount, 0, "Log file should not contain the default API host 'platform.activestate.com'")
305+
306+
// Clean up - remove the config setting
307+
cp = ts.Spawn("config", "set", constants.UpdateEndpointConfig, "")
308+
cp.Expect("Successfully")
309+
cp.ExpectExitCode(0)
310+
}
311+
312+
func (suite *UpdateIntegrationTestSuite) TestUpdateHost() {
313+
suite.OnlyRunForTags(tagsuite.Update)
314+
315+
ts := e2e.New(suite.T(), false)
316+
defer ts.Close()
317+
318+
cp := ts.Spawn("config", "set", constants.UpdateEndpointConfig, "https://example.com/update")
319+
cp.Expect("Successfully set config key")
320+
cp.ExpectExitCode(0)
321+
322+
cp = ts.SpawnWithOpts(
323+
e2e.OptArgs("update"),
324+
e2e.OptAppendEnv(suite.env(false, false)...),
325+
e2e.OptAppendEnv("VERBOSE=true"),
326+
)
327+
cp.ExpectExitCode(0)
328+
329+
output := cp.Snapshot()
330+
suite.Assert().Contains(output, "Getting update info: https://example.com/update/")
331+
}
332+
278333
func TestUpdateIntegrationTestSuite(t *testing.T) {
279334
if testing.Short() {
280335
t.Skip("skipping integration test in short mode.")

0 commit comments

Comments
 (0)