Skip to content

Commit fcc79dc

Browse files
committed
Allow configuration of API host
1 parent d29a589 commit fcc79dc

File tree

11 files changed

+167
-13
lines changed

11 files changed

+167
-13
lines changed

internal/constants/constants.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,6 @@ const SvcLogRotateIntervalEnvVarName = "ACTIVESTATE_CLI_LOG_ROTATE_INTERVAL_MS"
181181
// DisableActivateEventsEnvVarName is the environment variable used to disable events when activating or checking out a project
182182
const DisableActivateEventsEnvVarName = "ACTIVESTATE_CLI_DISABLE_ACTIVATE_EVENTS"
183183

184-
// APIUpdateInfoURL is the URL for our update info server
185-
const APIUpdateInfoURL = "https://platform.activestate.com/sv/state-update/api/v1"
186-
187184
// APIUpdateURL is the URL for our update files
188185
const APIUpdateURL = "https://state-tool.activestate.com/update/state"
189186

@@ -261,6 +258,9 @@ const VulnerabilitiesAPIPath = "/v13s/v1/graphql"
261258
// HasuraInventoryAPIPath is the path used for the hasura inventory api
262259
const HasuraInventoryAPIPath = "/sv/hasura-inventory/v1/graphql"
263260

261+
// UpdateInfoAPIPath is the path used for the update info api
262+
const UpdateInfoAPIPath = "/sv/state-update/api/v1"
263+
264264
// NotificationsInfoURL is the URL we check against to see what versions are deprecated
265265
const NotificationsInfoURL = "https://state-tool.s3.amazonaws.com/messages.json"
266266

@@ -409,6 +409,9 @@ const SecurityPromptConfig = "security.prompt.enabled"
409409
// SecurityPromptLevelConfig is the config key used to determine the level of security prompts
410410
const SecurityPromptLevelConfig = "security.prompt.level"
411411

412+
// APIHostConfig is the config key used to determine the api host
413+
const APIHostConfig = "api.host"
414+
412415
// SvcAppName is the name we give our state-svc application
413416
const SvcAppName = "State Service"
414417

internal/runbits/checkout/checkout.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/ActiveState/cli/internal/runbits/buildscript"
1515
"github.com/ActiveState/cli/internal/runbits/git"
1616
"github.com/ActiveState/cli/pkg/localcommit"
17+
"github.com/ActiveState/cli/pkg/platform/api"
1718
"github.com/ActiveState/cli/pkg/platform/api/mono/mono_models"
1819
"github.com/ActiveState/cli/pkg/platform/authentication"
1920
"github.com/ActiveState/cli/pkg/platform/model"
@@ -193,6 +194,7 @@ func CreateProjectFiles(checkoutPath, cachePath, owner, name, branch, commitID,
193194
Language: language,
194195
Cache: cachePath,
195196
Portable: portable,
197+
Host: api.HostOverride(),
196198
})
197199
if err != nil {
198200
if osutils.IsAccessDeniedError(err) {

internal/runbits/runtime/runtime.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/ActiveState/cli/internal/runbits/runtime/trigger"
2828
"github.com/ActiveState/cli/pkg/buildplan"
2929
"github.com/ActiveState/cli/pkg/localcommit"
30+
"github.com/ActiveState/cli/pkg/platform/api"
3031
"github.com/ActiveState/cli/pkg/platform/model"
3132
bpModel "github.com/ActiveState/cli/pkg/platform/model/buildplanner"
3233
"github.com/ActiveState/cli/pkg/project"
@@ -264,7 +265,7 @@ func Update(
264265
// Build progress URL is of the form
265266
// https://<host>/<owner>/<project>/distributions?branch=<branch>&commitID=<commitID>
266267
host := constants.DefaultAPIHost
267-
if hostOverride := os.Getenv(constants.APIHostEnvVarName); hostOverride != "" {
268+
if hostOverride := api.HostOverride(); hostOverride != "" {
268269
host = hostOverride
269270
}
270271
path, err := url.JoinPath(proj.Owner(), proj.Name(), constants.BuildProgressUrlPathName)

internal/runners/initialize/init.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/ActiveState/cli/internal/runbits/runtime"
2626
"github.com/ActiveState/cli/internal/runbits/runtime/trigger"
2727
"github.com/ActiveState/cli/pkg/localcommit"
28+
"github.com/ActiveState/cli/pkg/platform/api"
2829
"github.com/ActiveState/cli/pkg/platform/authentication"
2930
"github.com/ActiveState/cli/pkg/platform/model"
3031
bpModel "github.com/ActiveState/cli/pkg/platform/model/buildplanner"
@@ -186,6 +187,7 @@ func (r *Initialize) Run(params *RunParams) (rerr error) {
186187
Language: lang.String(),
187188
Directory: path,
188189
Private: params.Private,
190+
Host: api.HostOverride(),
189191
}
190192

191193
pjfile, err := projectfile.Create(createParams)

internal/testhelpers/e2e/session.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ type Session struct {
6565
spawned []*SpawnedCmd
6666
ignoreLogErrors bool
6767
cache keyCache
68+
cfg *config.Instance
6869
}
6970

7071
type keyCache map[string]string
@@ -203,6 +204,7 @@ func new(t *testing.T, retainDirs, updatePath bool, extraEnv ...string) *Session
203204
if err := cfg.Set(constants.SecurityPromptConfig, false); err != nil {
204205
require.NoError(session.T, err)
205206
}
207+
session.cfg = cfg
206208

207209
return session
208210
}
@@ -790,6 +792,10 @@ func (s *Session) SetupRCFileCustom(subshell subshell.SubShell) {
790792
require.NoError(s.T, err)
791793
}
792794

795+
func (s *Session) SetConfig(key string, value interface{}) {
796+
require.NoError(s.T, s.cfg.Set(key, value))
797+
}
798+
793799
func RunningOnCI() bool {
794800
return condition.OnCI()
795801
}

internal/updater/checker.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import (
1313
"github.com/ActiveState/cli/internal/analytics"
1414
anaConst "github.com/ActiveState/cli/internal/analytics/constants"
1515
"github.com/ActiveState/cli/internal/analytics/dimensions"
16-
"github.com/ActiveState/cli/internal/constants"
1716
"github.com/ActiveState/cli/internal/errs"
1817
"github.com/ActiveState/cli/internal/logging"
1918
"github.com/ActiveState/cli/internal/retryhttp"
2019
"github.com/ActiveState/cli/internal/rtutils/ptr"
20+
"github.com/ActiveState/cli/pkg/platform/api"
2121
)
2222

2323
type Configurable interface {
@@ -44,7 +44,7 @@ type Checker struct {
4444
}
4545

4646
func NewDefaultChecker(cfg Configurable, an analytics.Dispatcher) *Checker {
47-
infoURL := constants.APIUpdateInfoURL
47+
infoURL := api.GetServiceURL(api.ServiceUpdateInfo).String()
4848
if url, ok := os.LookupEnv("_TEST_UPDATE_INFO_URL"); ok {
4949
infoURL = url
5050
}

pkg/platform/api/settings.go

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,19 @@ import (
55
"os"
66
"strings"
77

8+
configMediator "github.com/ActiveState/cli/internal/mediators/config"
89
"github.com/ActiveState/cli/pkg/projectfile"
910

1011
"github.com/ActiveState/cli/internal/condition"
12+
"github.com/ActiveState/cli/internal/config"
1113
"github.com/ActiveState/cli/internal/constants"
1214
"github.com/ActiveState/cli/internal/logging"
1315
)
1416

17+
func init() {
18+
configMediator.RegisterOption(constants.APIHostConfig, configMediator.String, "")
19+
}
20+
1521
// Service records available api services
1622
type Service string
1723

@@ -49,6 +55,9 @@ const (
4955
// ServiceHasuraInventory is the Hasura service for inventory information.
5056
ServiceHasuraInventory = "hasura-inventory"
5157

58+
// ServiceUpdateInfo is the service for update info
59+
ServiceUpdateInfo = "update-info"
60+
5261
// TestingPlatform is the API host used by tests so-as not to affect production.
5362
TestingPlatform = ".testing.tld"
5463
)
@@ -109,6 +118,11 @@ var urlsByService = map[Service]*url.URL{
109118
Host: constants.DefaultAPIHost,
110119
Path: constants.HasuraInventoryAPIPath,
111120
},
121+
ServiceUpdateInfo: {
122+
Scheme: "https",
123+
Host: constants.DefaultAPIHost,
124+
Path: constants.UpdateInfoAPIPath,
125+
},
112126
}
113127

114128
// GetServiceURL returns the URL for the given service
@@ -121,7 +135,7 @@ func GetServiceURL(service Service) *url.URL {
121135
serviceURL.Host = *host
122136
}
123137

124-
if insecure := os.Getenv(constants.APIInsecureEnvVarName); insecure == "true" {
138+
if insecure := getProjectHostFromEnv(); insecure == "true" {
125139
if serviceURL.Scheme == "https" || serviceURL.Scheme == "wss" {
126140
serviceURL.Scheme = strings.TrimRight(serviceURL.Scheme, "s")
127141
}
@@ -142,8 +156,9 @@ func GetServiceURL(service Service) *url.URL {
142156
}
143157

144158
func getProjectHost(service Service) *string {
145-
if apiHost := os.Getenv(constants.APIHostEnvVarName); apiHost != "" {
146-
return &apiHost
159+
if host := HostOverride(); host != "" {
160+
logging.Debug("Using host override: %s", host)
161+
return &host
147162
}
148163

149164
if condition.InUnitTest() {
@@ -164,11 +179,35 @@ func getProjectHost(service Service) *string {
164179
return &url.Host
165180
}
166181

182+
func getProjectHostFromConfig() string {
183+
cfg, err := config.New()
184+
if err != nil {
185+
return ""
186+
}
187+
return cfg.GetString(constants.APIHostConfig)
188+
}
189+
190+
func getProjectHostFromEnv() string {
191+
return os.Getenv(constants.APIHostEnvVarName)
192+
}
193+
194+
func HostOverride() string {
195+
if apiHost := getProjectHostFromEnv(); apiHost != "" {
196+
return apiHost
197+
}
198+
199+
if apiHost := getProjectHostFromConfig(); apiHost != "" {
200+
return apiHost
201+
}
202+
203+
return ""
204+
}
205+
167206
// GetPlatformURL returns a generic Platform URL for the given path.
168207
// This is for retrieving non-service URLs (e.g. signup URL).
169208
func GetPlatformURL(path string) *url.URL {
170209
host := constants.DefaultAPIHost
171-
if hostOverride := os.Getenv(constants.APIHostEnvVarName); hostOverride != "" {
210+
if hostOverride := HostOverride(); hostOverride != "" {
172211
host = hostOverride
173212
}
174213
return &url.URL{

pkg/projectfile/create_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ func Test_Create(t *testing.T) {
3838
Project: tt.args.project,
3939
Directory: tt.args.directory,
4040
Language: tt.args.language,
41+
Host: "test.example.com",
4142
})
4243
assert.NoError(t, err)
4344
configFile := filepath.Join(tempDir, constants.ConfigFileName)

pkg/projectfile/projectfile.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -922,6 +922,7 @@ type CreateParams struct {
922922
ProjectURL string
923923
Cache string
924924
Portable bool
925+
Host string
925926
}
926927

927928
// Create will create a new activestate.yaml with a projectURL for the given details
@@ -943,9 +944,9 @@ func createCustom(params *CreateParams, lang language.Language) (*Project, error
943944

944945
if params.ProjectURL == "" {
945946
// Note: cannot use api.GetPlatformURL() due to import cycle.
946-
host := constants.DefaultAPIHost
947-
if hostOverride := os.Getenv(constants.APIHostEnvVarName); hostOverride != "" {
948-
host = hostOverride
947+
host := params.Host
948+
if host == "" {
949+
host = constants.DefaultAPIHost
949950
}
950951
u, err := url.Parse(fmt.Sprintf("https://%s/%s/%s", host, params.Owner, params.Project))
951952
if err != nil {

test/integration/api_int_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,77 @@ func (suite *ApiIntegrationTestSuite) TestNoApiCallsForPlainInvocation() {
5656
suite.Assert().True(readLogFile, "did not read log file")
5757
}
5858

59+
func (suite *ApiIntegrationTestSuite) TestAPIHostConfig_SetBeforeInvocation() {
60+
suite.OnlyRunForTags(tagsuite.Critical)
61+
62+
ts := e2e.New(suite.T(), false)
63+
defer ts.Close()
64+
65+
ts.SetConfig("api.host", "test.example.com")
66+
67+
cp := ts.SpawnWithOpts(
68+
e2e.OptArgs("checkout", "doesnt/exist"),
69+
e2e.OptAppendEnv("VERBOSE=true"),
70+
)
71+
cp.ExpectExitCode(11) // We know this command will fail, but we want to check the log file
72+
ts.IgnoreLogErrors()
73+
74+
correctHostCount := 0
75+
incorrectHostCount := 0
76+
for _, path := range ts.LogFiles() {
77+
contents := string(fileutils.ReadFileUnsafe(path))
78+
if strings.Contains(contents, "test.example.com") {
79+
correctHostCount++
80+
}
81+
if strings.Contains(contents, "platform.activestate.com") {
82+
incorrectHostCount++
83+
}
84+
}
85+
suite.Assert().Greater(correctHostCount, 0, "Log file should contain the configured API host 'test.example.com'")
86+
suite.Assert().Equal(incorrectHostCount, 0, "Log file should not contain the default API host 'platform.activestate.com'")
87+
88+
// Clean up - remove the config setting
89+
cp = ts.Spawn("config", "set", "api.host", "")
90+
cp.Expect("Successfully")
91+
cp.ExpectExitCode(0)
92+
}
93+
94+
func (suite *ApiIntegrationTestSuite) TestAPIHostConfig_SetOnFirstInvocation() {
95+
suite.OnlyRunForTags(tagsuite.Critical)
96+
97+
ts := e2e.New(suite.T(), false)
98+
defer ts.Close()
99+
100+
cp := ts.Spawn("config", "set", "api.host", "test.example.com")
101+
cp.Expect("Successfully")
102+
cp.ExpectExitCode(0)
103+
104+
cp = ts.SpawnWithOpts(
105+
e2e.OptArgs("checkout", "doesnt/exist"),
106+
e2e.OptAppendEnv("VERBOSE=true"),
107+
)
108+
cp.ExpectExitCode(11) // We know this command will fail, but we want to check the log file
109+
ts.IgnoreLogErrors()
110+
111+
// Because the config value is set on first invocation of the state tool the state-svc will start
112+
// before the state tool has a chance to set the host in the config. This means that it will still
113+
// use the default host. The above test confirms that the service will use the configured host if
114+
// the config is set before the state tool is invoked.
115+
correctHostCount := 0
116+
for _, path := range ts.LogFiles() {
117+
contents := string(fileutils.ReadFileUnsafe(path))
118+
if strings.Contains(contents, "test.example.com") {
119+
correctHostCount++
120+
}
121+
}
122+
suite.Assert().Greater(correctHostCount, 0, "Log file should contain the configured API host 'test.example.com'")
123+
124+
// Clean up - remove the config setting
125+
cp = ts.Spawn("config", "set", "api.host", "")
126+
cp.Expect("Successfully")
127+
cp.ExpectExitCode(0)
128+
}
129+
59130
func TestApiIntegrationTestSuite(t *testing.T) {
60131
suite.Run(t, new(ApiIntegrationTestSuite))
61132
}

0 commit comments

Comments
 (0)