Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/state-svc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/ActiveState/cli/internal/rollbar"
"github.com/ActiveState/cli/internal/runbits/panics"
"github.com/ActiveState/cli/internal/svcctl"
"github.com/ActiveState/cli/pkg/platform/api"
"github.com/ActiveState/cli/pkg/platform/authentication"
"github.com/inconshreveable/mousetrap"
)
Expand Down Expand Up @@ -69,6 +70,7 @@ func main() {
}
rollbar.SetupRollbar(constants.StateServiceRollbarToken)
rollbar.SetConfig(cfg)
api.SetConfig(cfg)

if os.Getenv("VERBOSE") == "true" {
logging.CurrentHandler().SetVerbose(true)
Expand Down
2 changes: 2 additions & 0 deletions cmd/state/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"github.com/ActiveState/cli/internal/runbits/panics"
"github.com/ActiveState/cli/internal/subshell"
"github.com/ActiveState/cli/internal/svcctl"
"github.com/ActiveState/cli/pkg/platform/api"
secretsapi "github.com/ActiveState/cli/pkg/platform/api/secrets"
"github.com/ActiveState/cli/pkg/platform/authentication"
"github.com/ActiveState/cli/pkg/platform/model"
Expand Down Expand Up @@ -91,6 +92,7 @@ func main() {
return
}
rollbar.SetConfig(cfg)
api.SetConfig(cfg)

// Configuration options
// This should only be used if the config option is not exclusive to one package.
Expand Down
6 changes: 6 additions & 0 deletions internal/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,9 @@ const VulnerabilitiesAPIPath = "/v13s/v1/graphql"
// HasuraInventoryAPIPath is the path used for the hasura inventory api
const HasuraInventoryAPIPath = "/sv/hasura-inventory/v1/graphql"

// UpdateInfoAPIPath is the path used for the update info api
const UpdateInfoAPIPath = "/sv/state-update/api/v1"

// NotificationsInfoURL is the URL we check against to see what versions are deprecated
const NotificationsInfoURL = "https://state-tool.s3.amazonaws.com/messages.json"

Expand Down Expand Up @@ -409,6 +412,9 @@ const SecurityPromptConfig = "security.prompt.enabled"
// SecurityPromptLevelConfig is the config key used to determine the level of security prompts
const SecurityPromptLevelConfig = "security.prompt.level"

// APIHostConfig is the config key used to determine the api host
const APIHostConfig = "api.host"

// SvcAppName is the name we give our state-svc application
const SvcAppName = "State Service"

Expand Down
2 changes: 2 additions & 0 deletions internal/runbits/checkout/checkout.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/ActiveState/cli/internal/runbits/buildscript"
"github.com/ActiveState/cli/internal/runbits/git"
"github.com/ActiveState/cli/pkg/localcommit"
"github.com/ActiveState/cli/pkg/platform/api"
"github.com/ActiveState/cli/pkg/platform/api/mono/mono_models"
"github.com/ActiveState/cli/pkg/platform/authentication"
"github.com/ActiveState/cli/pkg/platform/model"
Expand Down Expand Up @@ -193,6 +194,7 @@ func CreateProjectFiles(checkoutPath, cachePath, owner, name, branch, commitID,
Language: language,
Cache: cachePath,
Portable: portable,
Host: api.HostOverride(),
})
if err != nil {
if osutils.IsAccessDeniedError(err) {
Expand Down
3 changes: 2 additions & 1 deletion internal/runbits/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/ActiveState/cli/internal/runbits/runtime/trigger"
"github.com/ActiveState/cli/pkg/buildplan"
"github.com/ActiveState/cli/pkg/localcommit"
"github.com/ActiveState/cli/pkg/platform/api"
"github.com/ActiveState/cli/pkg/platform/model"
bpModel "github.com/ActiveState/cli/pkg/platform/model/buildplanner"
"github.com/ActiveState/cli/pkg/project"
Expand Down Expand Up @@ -264,7 +265,7 @@ func Update(
// Build progress URL is of the form
// https://<host>/<owner>/<project>/distributions?branch=<branch>&commitID=<commitID>
host := constants.DefaultAPIHost
if hostOverride := os.Getenv(constants.APIHostEnvVarName); hostOverride != "" {
if hostOverride := api.HostOverride(); hostOverride != "" {
host = hostOverride
}
path, err := url.JoinPath(proj.Owner(), proj.Name(), constants.BuildProgressUrlPathName)
Expand Down
2 changes: 2 additions & 0 deletions internal/runners/initialize/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/ActiveState/cli/internal/runbits/runtime"
"github.com/ActiveState/cli/internal/runbits/runtime/trigger"
"github.com/ActiveState/cli/pkg/localcommit"
"github.com/ActiveState/cli/pkg/platform/api"
"github.com/ActiveState/cli/pkg/platform/authentication"
"github.com/ActiveState/cli/pkg/platform/model"
bpModel "github.com/ActiveState/cli/pkg/platform/model/buildplanner"
Expand Down Expand Up @@ -186,6 +187,7 @@ func (r *Initialize) Run(params *RunParams) (rerr error) {
Language: lang.String(),
Directory: path,
Private: params.Private,
Host: api.HostOverride(),
}

pjfile, err := projectfile.Create(createParams)
Expand Down
10 changes: 10 additions & 0 deletions internal/testhelpers/e2e/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ type Session struct {
spawned []*SpawnedCmd
ignoreLogErrors bool
cache keyCache
cfg *config.Instance
}

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

return session
}
Expand Down Expand Up @@ -790,6 +792,14 @@ func (s *Session) SetupRCFileCustom(subshell subshell.SubShell) {
require.NoError(s.T, err)
}

func (s *Session) SetConfig(key string, value interface{}) {
require.NoError(s.T, s.cfg.Set(key, value))
}

func (s *Session) GetConfig(key string) interface{} {
return s.cfg.Get(key)
}

func RunningOnCI() bool {
return condition.OnCI()
}
1 change: 1 addition & 0 deletions internal/testhelpers/tagsuite/tagsuite.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
const (
Activate = "activate"
Analytics = "analytics"
API = "api"
Artifacts = "artifacts"
Auth = "auth"
Automation = "automation"
Expand Down
48 changes: 44 additions & 4 deletions pkg/platform/api/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@ import (
"os"
"strings"

configMediator "github.com/ActiveState/cli/internal/mediators/config"
"github.com/ActiveState/cli/pkg/projectfile"

"github.com/ActiveState/cli/internal/condition"
"github.com/ActiveState/cli/internal/config"
"github.com/ActiveState/cli/internal/constants"
"github.com/ActiveState/cli/internal/logging"
)

func init() {
configMediator.RegisterOption(constants.APIHostConfig, configMediator.String, "")
}

// Service records available api services
type Service string

Expand Down Expand Up @@ -49,6 +55,9 @@ const (
// ServiceHasuraInventory is the Hasura service for inventory information.
ServiceHasuraInventory = "hasura-inventory"

// ServiceUpdateInfo is the service for update info
ServiceUpdateInfo = "update-info"

// TestingPlatform is the API host used by tests so-as not to affect production.
TestingPlatform = ".testing.tld"
)
Expand Down Expand Up @@ -109,6 +118,17 @@ var urlsByService = map[Service]*url.URL{
Host: constants.DefaultAPIHost,
Path: constants.HasuraInventoryAPIPath,
},
ServiceUpdateInfo: {
Scheme: "https",
Host: constants.DefaultAPIHost,
Path: constants.UpdateInfoAPIPath,
},
}

var currentCfg *config.Instance

func SetConfig(config *config.Instance) {
currentCfg = config
}

// GetServiceURL returns the URL for the given service
Expand All @@ -121,7 +141,7 @@ func GetServiceURL(service Service) *url.URL {
serviceURL.Host = *host
}

if insecure := os.Getenv(constants.APIInsecureEnvVarName); insecure == "true" {
if insecure := os.Getenv(constants.APIHostEnvVarName); insecure == "true" {
if serviceURL.Scheme == "https" || serviceURL.Scheme == "wss" {
serviceURL.Scheme = strings.TrimRight(serviceURL.Scheme, "s")
}
Expand All @@ -142,8 +162,9 @@ func GetServiceURL(service Service) *url.URL {
}

func getProjectHost(service Service) *string {
if apiHost := os.Getenv(constants.APIHostEnvVarName); apiHost != "" {
return &apiHost
if host := HostOverride(); host != "" {
logging.Debug("Using host override: %s", host)
return &host
}

if condition.InUnitTest() {
Expand All @@ -164,11 +185,30 @@ func getProjectHost(service Service) *string {
return &url.Host
}

func getProjectHostFromConfig() string {
if currentCfg != nil && !currentCfg.Closed() {
return currentCfg.GetString(constants.APIHostConfig)
}
return ""
}

func HostOverride() string {
if apiHost := os.Getenv(constants.APIHostEnvVarName); apiHost != "" {
return apiHost
}

if apiHost := getProjectHostFromConfig(); apiHost != "" {
return apiHost
}

return ""
}

// GetPlatformURL returns a generic Platform URL for the given path.
// This is for retrieving non-service URLs (e.g. signup URL).
func GetPlatformURL(path string) *url.URL {
host := constants.DefaultAPIHost
if hostOverride := os.Getenv(constants.APIHostEnvVarName); hostOverride != "" {
if hostOverride := HostOverride(); hostOverride != "" {
host = hostOverride
}
return &url.URL{
Expand Down
1 change: 1 addition & 0 deletions pkg/projectfile/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func Test_Create(t *testing.T) {
Project: tt.args.project,
Directory: tt.args.directory,
Language: tt.args.language,
Host: "test.example.com",
})
assert.NoError(t, err)
configFile := filepath.Join(tempDir, constants.ConfigFileName)
Expand Down
7 changes: 4 additions & 3 deletions pkg/projectfile/projectfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,7 @@ type CreateParams struct {
ProjectURL string
Cache string
Portable bool
Host string
}

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

if params.ProjectURL == "" {
// Note: cannot use api.GetPlatformURL() due to import cycle.
host := constants.DefaultAPIHost
if hostOverride := os.Getenv(constants.APIHostEnvVarName); hostOverride != "" {
host = hostOverride
host := params.Host
if host == "" {
host = constants.DefaultAPIHost
}
u, err := url.Parse(fmt.Sprintf("https://%s/%s/%s", host, params.Owner, params.Project))
if err != nil {
Expand Down
74 changes: 74 additions & 0 deletions test/integration/api_int_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,80 @@ func (suite *ApiIntegrationTestSuite) TestNoApiCallsForPlainInvocation() {
suite.Assert().True(readLogFile, "did not read log file")
}

func (suite *ApiIntegrationTestSuite) TestAPIHostConfig_SetBeforeInvocation() {
suite.OnlyRunForTags(tagsuite.API)

ts := e2e.New(suite.T(), false)
defer ts.Close()

ts.SetConfig("api.host", "test.example.com")
suite.Assert().Equal(ts.GetConfig("api.host"), "test.example.com")

cp := ts.SpawnWithOpts(
e2e.OptArgs("checkout", "doesnt/exist"),
e2e.OptAppendEnv("VERBOSE=true"),
)
cp.ExpectExitCode(11) // We know this command will fail, but we want to check the log file
ts.IgnoreLogErrors()

correctHostCount := 0
incorrectHostCount := 0
for _, path := range ts.LogFiles() {
contents := string(fileutils.ReadFileUnsafe(path))
if strings.Contains(contents, "test.example.com") {
correctHostCount++
}
if strings.Contains(contents, "platform.activestate.com") {
incorrectHostCount++
}
}
suite.Assert().Greater(correctHostCount, 0, "Log file should contain the configured API host 'test.example.com'")
// TODO: This is failing because the state-svc is trying to update with the default host.
// This will be addressed by CP-1054 very shortly.
// suite.Assert().Equal(incorrectHostCount, 0, "Log file should not contain the default API host 'platform.activestate.com'")

// Clean up - remove the config setting
cp = ts.Spawn("config", "set", "api.host", "")
cp.Expect("Successfully")
cp.ExpectExitCode(0)
}

func (suite *ApiIntegrationTestSuite) TestAPIHostConfig_SetOnFirstInvocation() {
suite.OnlyRunForTags(tagsuite.API)

ts := e2e.New(suite.T(), false)
defer ts.Close()

cp := ts.Spawn("config", "set", "api.host", "test.example.com")
cp.Expect("Successfully")
cp.ExpectExitCode(0)

cp = ts.SpawnWithOpts(
e2e.OptArgs("checkout", "doesnt/exist"),
e2e.OptAppendEnv("VERBOSE=true"),
)
cp.ExpectExitCode(11) // We know this command will fail, but we want to check the log file
ts.IgnoreLogErrors()

// Because the config value is set on first invocation of the state tool the state-svc will start
// before the state tool has a chance to set the host in the config. This means that it will still
// use the default host. The above test confirms that the service will use the configured host if
// the config is set before the state tool is invoked.
correctHostCount := 0
for _, path := range ts.LogFiles() {
contents := string(fileutils.ReadFileUnsafe(path))
if strings.Contains(contents, "test.example.com") {
correctHostCount++
}
}
suite.Assert().Greater(correctHostCount, 0, "Log file should contain the configured API host 'test.example.com'")

// Clean up - remove the config setting
cp = ts.Spawn("config", "set", "api.host", "")
cp.Expect("Successfully")
cp.ExpectExitCode(0)
}

func TestApiIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(ApiIntegrationTestSuite))
}
32 changes: 32 additions & 0 deletions test/integration/config_int_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,38 @@ func (suite *ConfigIntegrationTestSuite) TestList() {

suite.Require().NotContains(cp.Snapshot(), constants.AsyncRuntimeConfig)
}

func (suite *ConfigIntegrationTestSuite) TestAPIHostConfig() {
suite.OnlyRunForTags(tagsuite.Config)
ts := e2e.New(suite.T(), false)
defer ts.Close()

cp := ts.Spawn("config", "set", "api.host", "test.example.com", "-o", "json")
cp.ExpectExitCode(0)
AssertValidJSON(suite.T(), cp)
cp.Expect(`{"name":"api.host","value":"test.example.com"}`)

cp = ts.Spawn("config", "get", "api.host", "-o", "json")
cp.ExpectExitCode(0)
AssertValidJSON(suite.T(), cp)
cp.Expect(`{"name":"api.host","value":"test.example.com"}`)

cp = ts.Spawn("config")
cp.Expect("api.host")
cp.Expect("test.example.com")
cp.ExpectExitCode(0)

cp = ts.Spawn("config", "set", "api.host", "", "-o", "json")
cp.ExpectExitCode(0)
AssertValidJSON(suite.T(), cp)
cp.Expect(`{"name":"api.host","value":""}`)

cp = ts.Spawn("config", "get", "api.host", "-o", "json")
cp.ExpectExitCode(0)
AssertValidJSON(suite.T(), cp)
cp.Expect(`{"name":"api.host","value":""}`)
}

func TestConfigIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(ConfigIntegrationTestSuite))
}
Loading