diff --git a/scw/client.go b/scw/client.go index 91d90119b..0bdb13bca 100644 --- a/scw/client.go +++ b/scw/client.go @@ -28,6 +28,7 @@ type Client struct { apiURL string userAgent string defaultOrganizationID *string + defaultProjectID *string defaultRegion *Region defaultZone *Zone defaultPageSize *uint32 @@ -76,6 +77,7 @@ func NewClient(opts ...ClientOption) (*Client, error) { apiURL: s.apiURL, userAgent: s.userAgent, defaultOrganizationID: s.defaultOrganizationID, + defaultProjectID: s.defaultProjectID, defaultRegion: s.defaultRegion, defaultZone: s.defaultZone, defaultPageSize: s.defaultPageSize, @@ -92,6 +94,16 @@ func (c *Client) GetDefaultOrganizationID() (organizationID string, exists bool) return "", false } +// GetDefaultProjectID returns the default project ID +// of the client. This value can be set in the client option +// WithDefaultProjectID(). Be aware this value can be empty. +func (c *Client) GetDefaultProjectID() (projectID string, exists bool) { + if c.defaultProjectID != nil { + return *c.defaultProjectID, true + } + return "", false +} + // GetDefaultRegion returns the default region of the client. // This value can be set in the client option // WithDefaultRegion(). Be aware this value can be empty. diff --git a/scw/client_option.go b/scw/client_option.go index bb3753455..f01b37400 100644 --- a/scw/client_option.go +++ b/scw/client_option.go @@ -94,6 +94,11 @@ func WithProfile(p *Profile) ClientOption { s.defaultOrganizationID = &organizationID } + if p.DefaultProjectID != nil { + projectID := *p.DefaultProjectID + s.defaultProjectID = &projectID + } + if p.DefaultRegion != nil { defaultRegion := Region(*p.DefaultRegion) s.defaultRegion = &defaultRegion @@ -120,6 +125,15 @@ func WithDefaultOrganizationID(organizationID string) ClientOption { } } +// WithDefaultProjectID client option sets the client default project ID. +// +// It will be used as the default value of the projectID field in all requests made with this client. +func WithDefaultProjectID(projectID string) ClientOption { + return func(s *settings) { + s.defaultProjectID = &projectID + } +} + // WithDefaultRegion client option sets the client default region. // // It will be used as the default value of the region field in all requests made with this client. @@ -155,6 +169,7 @@ type settings struct { httpClient httpClient insecure bool defaultOrganizationID *string + defaultProjectID *string defaultRegion *Region defaultZone *Zone defaultPageSize *uint32 @@ -201,6 +216,16 @@ func (s *settings) validate() error { } } + // Default Project ID. + if s.defaultProjectID != nil { + if *s.defaultProjectID == "" { + return NewInvalidClientOptionError("default project ID cannot be empty") + } + if !validation.IsProjectID(*s.defaultProjectID) { + return NewInvalidClientOptionError("invalid project ID format '%s', expected a UUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", *s.defaultProjectID) + } + } + // Default Region. if s.defaultRegion != nil { if *s.defaultRegion == "" { diff --git a/scw/client_option_test.go b/scw/client_option_test.go index 534019ea6..ffa07134f 100644 --- a/scw/client_option_test.go +++ b/scw/client_option_test.go @@ -11,6 +11,7 @@ import ( var ( defaultOrganizationID = "6170692e-7363-616c-6577-61792e636f6d" // hint: | xxd -ps -r + defaultProjectID = "6170692e-7363-616c-6577-61792e636f6d" // hint: | xxd -ps -r defaultRegion = RegionNlAms defaultZone = ZoneNlAms1 ) @@ -27,6 +28,7 @@ func TestClientOptions(t *testing.T) { s.token = auth.NewToken(v2ValidAccessKey, v2ValidSecretKey) s.apiURL = v2ValidAPIURL s.defaultOrganizationID = &defaultOrganizationID + s.defaultProjectID = &defaultProjectID s.defaultRegion = &defaultRegion s.defaultZone = &defaultZone }, @@ -83,6 +85,22 @@ func TestClientOptions(t *testing.T) { }, errStr: "scaleway-sdk-go: invalid organization ID format 'invalid', expected a UUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", }, + { + name: "Should throw an empty project ID error", + clientOption: func(s *settings) { + s.token = auth.NewToken(v2ValidAccessKey, v2ValidSecretKey) + s.defaultProjectID = StringPtr("") + }, + errStr: "scaleway-sdk-go: default project ID cannot be empty", + }, + { + name: "Should throw a bad project ID error", + clientOption: func(s *settings) { + s.token = auth.NewToken(v2ValidAccessKey, v2ValidSecretKey) + s.defaultProjectID = StringPtr(v2InvalidDefaultProjectID) + }, + errStr: "scaleway-sdk-go: invalid project ID format 'invalid', expected a UUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + }, { name: "Should throw a region error", clientOption: func(s *settings) { @@ -153,6 +171,7 @@ func TestCombinedClientOptions(t *testing.T) { expectedSecretKey string expectedAPIURL string expectedDefaultOrganizationID *string + expectedDefaultProjectID *string expectedDefaultRegion *Region expectedDefaultZone *Zone }{ @@ -164,6 +183,7 @@ func TestCombinedClientOptions(t *testing.T) { ScwSecretKeyEnv: v2ValidSecretKey2, ScwAPIURLEnv: v2ValidAPIURL2, ScwDefaultOrganizationIDEnv: v2ValidDefaultOrganizationID2, + ScwDefaultProjectIDEnv: v2ValidDefaultProjectID2, ScwDefaultRegionEnv: v2ValidDefaultRegion2, ScwDefaultZoneEnv: v2ValidDefaultZone2, }, @@ -174,6 +194,7 @@ func TestCombinedClientOptions(t *testing.T) { expectedSecretKey: v2ValidSecretKey2, expectedAPIURL: v2ValidAPIURL2, expectedDefaultOrganizationID: s(v2ValidDefaultOrganizationID2), + expectedDefaultProjectID: s(v2ValidDefaultProjectID2), expectedDefaultRegion: r(Region(v2ValidDefaultRegion2)), expectedDefaultZone: z(Zone(v2ValidDefaultZone2)), }, @@ -186,6 +207,7 @@ func TestCombinedClientOptions(t *testing.T) { ScwSecretKeyEnv: v2ValidSecretKey, ScwAPIURLEnv: v2ValidAPIURL, ScwDefaultOrganizationIDEnv: v2ValidDefaultOrganizationID, + ScwDefaultProjectIDEnv: v2ValidDefaultProjectID, ScwDefaultRegionEnv: v2ValidDefaultRegion, ScwDefaultZoneEnv: v2ValidDefaultZone, }, @@ -196,6 +218,7 @@ func TestCombinedClientOptions(t *testing.T) { expectedSecretKey: v2ValidSecretKey, expectedAPIURL: v2ValidAPIURL, expectedDefaultOrganizationID: s(v2ValidDefaultOrganizationID), + expectedDefaultProjectID: s(v2ValidDefaultProjectID), expectedDefaultRegion: r(Region(v2ValidDefaultRegion)), expectedDefaultZone: z(Zone(v2ValidDefaultZone)), }, @@ -230,6 +253,7 @@ func TestCombinedClientOptions(t *testing.T) { testhelpers.Equals(t, test.expectedSecretKey, client.auth.(*auth.Token).SecretKey) testhelpers.Equals(t, test.expectedAPIURL, client.apiURL) testhelpers.Equals(t, test.expectedDefaultOrganizationID, client.defaultOrganizationID) + testhelpers.Equals(t, test.expectedDefaultProjectID, client.defaultProjectID) testhelpers.Equals(t, test.expectedDefaultRegion, client.defaultRegion) testhelpers.Equals(t, test.expectedDefaultZone, client.defaultZone) // skip insecure tests diff --git a/scw/client_test.go b/scw/client_test.go index b19e5faa3..41eda81b3 100644 --- a/scw/client_test.go +++ b/scw/client_test.go @@ -18,6 +18,7 @@ const ( testAccessKey = "SCW1234567890ABCDEFG" testSecretKey = "7363616c-6577-6573-6862-6f7579616161" // hint: | xxd -ps -r testDefaultOrganizationID = "6170692e-7363-616c-6577-61792e636f6d" // hint: | xxd -ps -r + testDefaultProjectID = "6170692e-7363-616c-6577-61792e636f6e" testDefaultRegion = RegionFrPar testDefaultZone = ZoneFrPar1 testDefaultPageSize = uint32(5) @@ -60,6 +61,7 @@ func TestNewClientWithOptions(t *testing.T) { WithAuth(testAccessKey, testSecretKey), WithHTTPClient(someHTTPClient), WithDefaultOrganizationID(testDefaultOrganizationID), + WithDefaultProjectID(testDefaultProjectID), WithDefaultRegion(testDefaultRegion), WithDefaultZone(testDefaultZone), WithDefaultPageSize(testDefaultPageSize), @@ -77,6 +79,10 @@ func TestNewClientWithOptions(t *testing.T) { testhelpers.Equals(t, testDefaultOrganizationID, defaultOrganizationID) testhelpers.Assert(t, exist, "defaultOrganizationID must exist") + defaultProjectID, exist := client.GetDefaultProjectID() + testhelpers.Equals(t, testDefaultProjectID, defaultProjectID) + testhelpers.Assert(t, exist, "defaultProjectID must exist") + defaultRegion, exist := client.GetDefaultRegion() testhelpers.Equals(t, testDefaultRegion, defaultRegion) testhelpers.Assert(t, exist, "defaultRegion must exist") @@ -105,6 +111,7 @@ func TestNewClientWithOptions(t *testing.T) { s(testAPIURL), b(testInsecure), s(testDefaultOrganizationID), + s(testDefaultProjectID), s(string(testDefaultRegion)), s(string(testDefaultZone)), b(true), @@ -125,6 +132,10 @@ func TestNewClientWithOptions(t *testing.T) { testhelpers.Equals(t, testDefaultOrganizationID, defaultOrganizationID) testhelpers.Assert(t, exist, "defaultOrganizationID must exist") + defaultProjectID, exist := client.GetDefaultProjectID() + testhelpers.Equals(t, testDefaultProjectID, defaultProjectID) + testhelpers.Assert(t, exist, "defaultProjectID must exist") + defaultRegion, exist := client.GetDefaultRegion() testhelpers.Equals(t, testDefaultRegion, defaultRegion) testhelpers.Assert(t, exist, "defaultRegion must exist") diff --git a/scw/config.go b/scw/config.go index fd6ee216e..59cd95f71 100644 --- a/scw/config.go +++ b/scw/config.go @@ -39,6 +39,9 @@ const configFileTemplate = `# Scaleway configuration file # Your organization ID is the identifier of your account inside Scaleway infrastructure. {{ if .DefaultOrganizationID }}default_organization_id: {{ .DefaultOrganizationID }}{{ else }}# default_organization_id: 11111111-1111-1111-1111-111111111111{{ end }} +# Your project ID is the identifier of the project your resources are attached to (beta). +{{ if .DefaultProjectID }}default_project_id: {{ .DefaultProjectID }}{{ else }}# default_project_id: 11111111-1111-1111-1111-111111111111{{ end }} + # A region is represented as a geographical area such as France (Paris) or the Netherlands (Amsterdam). # It can contain multiple availability zones. # Example of region: fr-par, nl-ams @@ -81,6 +84,7 @@ profiles: {{ if $v.AccessKey }}access_key: {{ $v.AccessKey }}{{ else }}# access_key: SCW11111111111111111{{ end }} {{ if $v.SecretKey }}secret_key: {{ $v.SecretKey }}{{ else }}# secret_key: 11111111-1111-1111-1111-111111111111{{ end }} {{ if $v.DefaultOrganizationID }}default_organization_id: {{ $v.DefaultOrganizationID }}{{ else }}# default_organization_id: 11111111-1111-1111-1111-111111111111{{ end }} + {{ if $v.DefaultProjectID }}default_project_id: {{ $v.DefaultProjectID }}{{ else }}# default_project_id: 11111111-1111-1111-1111-111111111111{{ end }} {{ if $v.DefaultZone }}default_zone: {{ $v.DefaultZone }}{{ else }}# default_zone: fr-par-1{{ end }} {{ if $v.DefaultRegion }}default_region: {{ $v.DefaultRegion }}{{ else }}# default_region: fr-par{{ end }} {{ if $v.APIURL }}api_url: {{ $v.APIURL }}{{ else }}# api_url: https://api.scaleway.com{{ end }} @@ -91,7 +95,8 @@ profiles: # myProfile: # access_key: 11111111-1111-1111-1111-111111111111 # secret_key: 11111111-1111-1111-1111-111111111111 -# organization_id: 11111111-1111-1111-1111-111111111111 +# default_organization_id: 11111111-1111-1111-1111-111111111111 +# default_project_id: 11111111-1111-1111-1111-111111111111 # default_zone: fr-par-1 # default_region: fr-par # api_url: https://api.scaleway.com @@ -111,6 +116,7 @@ type Profile struct { APIURL *string `yaml:"api_url,omitempty"` Insecure *bool `yaml:"insecure,omitempty"` DefaultOrganizationID *string `yaml:"default_organization_id,omitempty"` + DefaultProjectID *string `yaml:"default_project_id,omitempty"` DefaultRegion *string `yaml:"default_region,omitempty"` DefaultZone *string `yaml:"default_zone,omitempty"` SendTelemetry *bool `yaml:"send_telemetry,omitempty"` @@ -294,6 +300,7 @@ func MergeProfiles(original *Profile, others ...*Profile) *Profile { APIURL: original.APIURL, Insecure: original.Insecure, DefaultOrganizationID: original.DefaultOrganizationID, + DefaultProjectID: original.DefaultProjectID, DefaultRegion: original.DefaultRegion, DefaultZone: original.DefaultZone, } @@ -314,6 +321,9 @@ func MergeProfiles(original *Profile, others ...*Profile) *Profile { if other.DefaultOrganizationID != nil { np.DefaultOrganizationID = other.DefaultOrganizationID } + if other.DefaultProjectID != nil { + np.DefaultProjectID = other.DefaultProjectID + } if other.DefaultRegion != nil { np.DefaultRegion = other.DefaultRegion } diff --git a/scw/config_legacy.go b/scw/config_legacy.go index e914fac26..9a81df685 100644 --- a/scw/config_legacy.go +++ b/scw/config_legacy.go @@ -36,6 +36,7 @@ func (v1 *configV1) toV2() *Config { return &Config{ Profile: Profile{ DefaultOrganizationID: &v1.Organization, + DefaultProjectID: &v1.Organization, // v1 config is not aware of project, so default project is set to organization ID SecretKey: &v1.Token, // ignore v1 version }, diff --git a/scw/config_test.go b/scw/config_test.go index 51f60819d..d704b0e47 100644 --- a/scw/config_test.go +++ b/scw/config_test.go @@ -21,6 +21,7 @@ var ( v2ValidInsecure2 = "true" v2ValidSendTelemetry2 = "true" v2ValidDefaultOrganizationID2 = "6d6f7264-6f72-6772-6561-74616761696e" // hint: | xxd -ps -r + v2ValidDefaultProjectID2 = "6d6f7264-6f72-6772-6561-74616761696f" v2ValidDefaultRegion2 = string(RegionFrPar) v2ValidDefaultZone2 = string(ZoneFrPar2) @@ -30,6 +31,7 @@ var ( v2ValidInsecure = "false" v2ValidSendTelemetry = "true" v2ValidDefaultOrganizationID = "6170692e-7363-616c-6577-61792e636f6d" // hint: | xxd -ps -r + v2ValidDefaultProjectID = "6170692e-7363-616c-6577-61792e636f6e" v2ValidDefaultRegion = string(RegionNlAms) v2ValidDefaultZone = string(ZoneNlAms1) v2ValidProfile = "flantier" @@ -37,6 +39,7 @@ var ( v2InvalidAccessKey = "invalid" v2InvalidSecretKey = "invalid" v2InvalidDefaultOrganizationID = "invalid" + v2InvalidDefaultProjectID = "invalid" v2InvalidDefaultRegion = "invalid" v2InvalidDefaultZone = "invalid" @@ -45,6 +48,7 @@ var ( AccessKey: &v2ValidAccessKey, SecretKey: &v2ValidSecretKey, DefaultOrganizationID: &v2ValidDefaultOrganizationID, + DefaultProjectID: &v2ValidDefaultProjectID, DefaultRegion: &v2ValidDefaultRegion, }, } @@ -54,6 +58,7 @@ secret_key: ` + v2ValidSecretKey + ` api_url: ` + v2ValidAPIURL + ` insecure: ` + v2ValidInsecure + ` default_organization_id: ` + v2ValidDefaultOrganizationID + ` +default_project_id: ` + v2ValidDefaultProjectID + ` default_region: ` + v2ValidDefaultRegion + ` default_zone: ` + v2ValidDefaultZone @@ -66,6 +71,7 @@ profiles: insecure: ` + v2ValidInsecure2 + ` send_telemetry: ` + v2ValidSendTelemetry2 + ` default_organization_id: ` + v2ValidDefaultOrganizationID2 + ` + default_project_id: ` + v2ValidDefaultProjectID2 + ` default_region: ` + v2ValidDefaultRegion2 + ` default_zone: ` + v2ValidDefaultZone2 + ` ` @@ -77,6 +83,7 @@ api_url: ` + v2ValidAPIURL + ` insecure: ` + v2ValidInsecure + ` send_telemetry: ` + v2ValidSendTelemetry2 + ` default_organization_id: ` + v2ValidDefaultOrganizationID + ` +default_project_id: ` + v2ValidDefaultProjectID + ` default_region: ` + v2ValidDefaultRegion + ` default_zone: ` + v2ValidDefaultZone + ` active_profile: ` + v2ValidProfile + ` @@ -87,6 +94,7 @@ profiles: api_url: ` + v2ValidAPIURL2 + ` insecure: ` + v2ValidInsecure2 + ` default_organization_id: ` + v2ValidDefaultOrganizationID2 + ` + default_project_id: ` + v2ValidDefaultProjectID2 + ` default_region: ` + v2ValidDefaultRegion2 + ` default_zone: ` + v2ValidDefaultZone2 + ` ` @@ -98,6 +106,7 @@ api_url: ` + v2ValidAPIURL + ` insecure: ` + v2ValidInsecure + ` send_telemetry: ` + v2ValidSendTelemetry + ` default_organization_id: ` + v2ValidDefaultOrganizationID + ` +default_project_id: ` + v2ValidDefaultProjectID + ` default_region: ` + v2ValidDefaultRegion + ` default_zone: ` + v2ValidDefaultZone + ` active_profile: ` + v2ValidProfile + ` @@ -111,6 +120,7 @@ profiles: access_key: ` + v2ValidAccessKey + ` secret_key: ` + v2ValidSecretKey + ` default_organization_id: ` + v2ValidDefaultOrganizationID + ` +default_project_id: ` + v2ValidDefaultProjectID + ` default_region: ` + v2ValidDefaultRegion + ` ` @@ -119,6 +129,7 @@ default_region: ` + v2ValidDefaultRegion + ` v2FromV1ConfigFile = `secret_key: ` + v1ValidToken + ` default_organization_id: ` + v1ValidOrganizationID + ` +default_project_id: ` + v1ValidOrganizationID + ` ` ) @@ -146,6 +157,7 @@ func TestSaveConfig(t *testing.T) { AccessKey: s(v2ValidAccessKey), SecretKey: s(v2ValidSecretKey), DefaultOrganizationID: s(v2ValidDefaultOrganizationID), + DefaultProjectID: s(v2ValidDefaultProjectID), DefaultRegion: s(v2ValidDefaultRegion), }, }, @@ -163,6 +175,7 @@ func TestSaveConfig(t *testing.T) { AccessKey: s(v2ValidAccessKey), SecretKey: s(v2ValidSecretKey), DefaultOrganizationID: s(v2ValidDefaultOrganizationID), + DefaultProjectID: s(v2ValidDefaultProjectID), DefaultRegion: s(v2ValidDefaultRegion), }, }, @@ -204,6 +217,7 @@ func TestSaveConfig(t *testing.T) { APIURL: s(v2ValidAPIURL2), Insecure: b(true), DefaultOrganizationID: s(v2ValidDefaultOrganizationID2), + DefaultProjectID: s(v2ValidDefaultProjectID2), DefaultRegion: s(v2ValidDefaultRegion2), DefaultZone: s(v2ValidDefaultZone2), }} @@ -259,6 +273,7 @@ func TestLoadProfileAndActiveProfile(t *testing.T) { expectedInsecure *bool expectedSendTelemetry *bool expectedDefaultOrganizationID *string + expectedDefaultProjectID *string expectedDefaultRegion *string expectedDefaultZone *string }{ @@ -294,6 +309,7 @@ func TestLoadProfileAndActiveProfile(t *testing.T) { expectedAccessKey: s(v2ValidAccessKey), expectedSecretKey: s(v2ValidSecretKey), expectedDefaultOrganizationID: s(v2ValidDefaultOrganizationID), + expectedDefaultProjectID: s(v2ValidDefaultProjectID), expectedDefaultRegion: s(v2ValidDefaultRegion), }, { @@ -307,6 +323,7 @@ func TestLoadProfileAndActiveProfile(t *testing.T) { expectedAccessKey: s(v2ValidAccessKey), expectedSecretKey: s(v2ValidSecretKey), expectedDefaultOrganizationID: s(v2ValidDefaultOrganizationID), + expectedDefaultProjectID: s(v2ValidDefaultProjectID), expectedDefaultRegion: s(v2ValidDefaultRegion), }, { @@ -320,6 +337,7 @@ func TestLoadProfileAndActiveProfile(t *testing.T) { isMigrated: true, expectedSecretKey: s(v1ValidToken), expectedDefaultOrganizationID: s(v1ValidOrganizationID), + expectedDefaultProjectID: s(v1ValidOrganizationID), }, { name: "Simple config with valid V2 and valid V1", @@ -333,6 +351,7 @@ func TestLoadProfileAndActiveProfile(t *testing.T) { expectedAccessKey: s(v2ValidAccessKey), expectedSecretKey: s(v2ValidSecretKey), expectedDefaultOrganizationID: s(v2ValidDefaultOrganizationID), + expectedDefaultProjectID: s(v2ValidDefaultProjectID), expectedDefaultRegion: s(v2ValidDefaultRegion), }, { @@ -348,6 +367,7 @@ func TestLoadProfileAndActiveProfile(t *testing.T) { expectedAPIURL: s(v2ValidAPIURL), expectedInsecure: b(false), expectedDefaultOrganizationID: s(v2ValidDefaultOrganizationID), + expectedDefaultProjectID: s(v2ValidDefaultProjectID), expectedDefaultRegion: s(v2ValidDefaultRegion), expectedDefaultZone: s(v2ValidDefaultZone), }, @@ -364,6 +384,7 @@ func TestLoadProfileAndActiveProfile(t *testing.T) { expectedAPIURL: s(v2ValidAPIURL2), expectedInsecure: b(true), expectedDefaultOrganizationID: s(v2ValidDefaultOrganizationID2), + expectedDefaultProjectID: s(v2ValidDefaultProjectID2), expectedDefaultRegion: s(v2ValidDefaultRegion2), expectedDefaultZone: s(v2ValidDefaultZone2), expectedSendTelemetry: b(true), @@ -381,6 +402,7 @@ func TestLoadProfileAndActiveProfile(t *testing.T) { expectedAPIURL: s(v2ValidAPIURL), expectedInsecure: b(false), expectedDefaultOrganizationID: s(v2ValidDefaultOrganizationID), + expectedDefaultProjectID: s(v2ValidDefaultProjectID), expectedDefaultRegion: s(v2ValidDefaultRegion), expectedDefaultZone: s(v2ValidDefaultZone), expectedSendTelemetry: b(true), @@ -399,6 +421,7 @@ func TestLoadProfileAndActiveProfile(t *testing.T) { expectedAPIURL: s(v2ValidAPIURL2), expectedInsecure: b(true), expectedDefaultOrganizationID: s(v2ValidDefaultOrganizationID2), + expectedDefaultProjectID: s(v2ValidDefaultProjectID2), expectedDefaultRegion: s(v2ValidDefaultRegion2), expectedDefaultZone: s(v2ValidDefaultZone2), }, @@ -462,6 +485,7 @@ func TestLoadProfileAndActiveProfile(t *testing.T) { testhelpers.Equals(t, test.expectedSecretKey, p.SecretKey) testhelpers.Equals(t, test.expectedAPIURL, p.APIURL) testhelpers.Equals(t, test.expectedDefaultOrganizationID, p.DefaultOrganizationID) + testhelpers.Equals(t, test.expectedDefaultProjectID, p.DefaultProjectID) testhelpers.Equals(t, test.expectedDefaultRegion, p.DefaultRegion) testhelpers.Equals(t, test.expectedDefaultZone, p.DefaultZone) testhelpers.Equals(t, test.expectedInsecure, p.Insecure) @@ -631,6 +655,9 @@ func TestConfig_ConfigFile(t *testing.T) { # Your organization ID is the identifier of your account inside Scaleway infrastructure. # default_organization_id: 11111111-1111-1111-1111-111111111111 +# Your project ID is the identifier of the project your resources are attached to (beta). +# default_project_id: 11111111-1111-1111-1111-111111111111 + # A region is represented as a geographical area such as France (Paris) or the Netherlands (Amsterdam). # It can contain multiple availability zones. # Example of region: fr-par, nl-ams @@ -671,7 +698,8 @@ func TestConfig_ConfigFile(t *testing.T) { # myProfile: # access_key: 11111111-1111-1111-1111-111111111111 # secret_key: 11111111-1111-1111-1111-111111111111 -# organization_id: 11111111-1111-1111-1111-111111111111 +# default_organization_id: 11111111-1111-1111-1111-111111111111 +# default_project_id: 11111111-1111-1111-1111-111111111111 # default_zone: fr-par-1 # default_region: fr-par # api_url: https://api.scaleway.com @@ -705,6 +733,9 @@ access_key: SCW1234567890ABCDEFG # Your organization ID is the identifier of your account inside Scaleway infrastructure. # default_organization_id: 11111111-1111-1111-1111-111111111111 +# Your project ID is the identifier of the project your resources are attached to (beta). +# default_project_id: 11111111-1111-1111-1111-111111111111 + # A region is represented as a geographical area such as France (Paris) or the Netherlands (Amsterdam). # It can contain multiple availability zones. # Example of region: fr-par, nl-ams @@ -745,7 +776,8 @@ access_key: SCW1234567890ABCDEFG # myProfile: # access_key: 11111111-1111-1111-1111-111111111111 # secret_key: 11111111-1111-1111-1111-111111111111 -# organization_id: 11111111-1111-1111-1111-111111111111 +# default_organization_id: 11111111-1111-1111-1111-111111111111 +# default_project_id: 11111111-1111-1111-1111-111111111111 # default_zone: fr-par-1 # default_region: fr-par # api_url: https://api.scaleway.com @@ -793,6 +825,9 @@ secret_key: 7363616c-6577-6573-6862-6f7579616161 # Your organization ID is the identifier of your account inside Scaleway infrastructure. # default_organization_id: 11111111-1111-1111-1111-111111111111 +# Your project ID is the identifier of the project your resources are attached to (beta). +# default_project_id: 11111111-1111-1111-1111-111111111111 + # A region is represented as a geographical area such as France (Paris) or the Netherlands (Amsterdam). # It can contain multiple availability zones. # Example of region: fr-par, nl-ams @@ -834,6 +869,7 @@ profiles: access_key: SCW234567890ABCDEFGH secret_key: 6f6e6574-6f72-756c-6c74-68656d616c6c # default_organization_id: 11111111-1111-1111-1111-111111111111 + # default_project_id: 11111111-1111-1111-1111-111111111111 # default_zone: fr-par-1 # default_region: fr-par # api_url: https://api.scaleway.com @@ -843,6 +879,7 @@ profiles: access_key: SCW234567890ABCDEFGH secret_key: 6f6e6574-6f72-756c-6c74-68656d616c6c # default_organization_id: 11111111-1111-1111-1111-111111111111 + # default_project_id: 11111111-1111-1111-1111-111111111111 # default_zone: fr-par-1 # default_region: fr-par # api_url: https://api.scaleway.com diff --git a/scw/env.go b/scw/env.go index bbfeee597..8672d8351 100644 --- a/scw/env.go +++ b/scw/env.go @@ -18,6 +18,7 @@ const ( ScwAPIURLEnv = "SCW_API_URL" ScwInsecureEnv = "SCW_INSECURE" ScwDefaultOrganizationIDEnv = "SCW_DEFAULT_ORGANIZATION_ID" + ScwDefaultProjectIDEnv = "SCW_DEFAULT_PROJECT_ID" ScwDefaultRegionEnv = "SCW_DEFAULT_REGION" ScwDefaultZoneEnv = "SCW_DEFAULT_ZONE" @@ -90,6 +91,11 @@ func LoadEnvProfile() *Profile { p.DefaultOrganizationID = &organizationID } + projectID, _, envExist := getEnv(ScwDefaultProjectIDEnv) + if envExist { + p.DefaultProjectID = &projectID + } + region, _, envExist := getEnv(ScwDefaultRegionEnv, cliRegionEnv, terraformRegionEnv) if envExist { region = v1RegionToV2(region) diff --git a/scw/env_test.go b/scw/env_test.go index e46533d9d..fb3e77dab 100644 --- a/scw/env_test.go +++ b/scw/env_test.go @@ -18,6 +18,7 @@ func TestLoadEnvProfile(t *testing.T) { expectedAPIURL *string expectedInsecure *bool expectedDefaultOrganizationID *string + expectedDefaultProjectID *string expectedDefaultRegion *string expectedDefaultZone *string }{ @@ -30,6 +31,7 @@ func TestLoadEnvProfile(t *testing.T) { ScwAPIURLEnv: v2ValidAPIURL, ScwInsecureEnv: "false", ScwDefaultOrganizationIDEnv: v2ValidDefaultOrganizationID, + ScwDefaultProjectIDEnv: v2ValidDefaultProjectID, ScwDefaultRegionEnv: v2ValidDefaultRegion, ScwDefaultZoneEnv: v2ValidDefaultZone, }, @@ -38,6 +40,7 @@ func TestLoadEnvProfile(t *testing.T) { expectedAPIURL: s(v2ValidAPIURL), expectedInsecure: b(false), expectedDefaultOrganizationID: s(v2ValidDefaultOrganizationID), + expectedDefaultProjectID: s(v2ValidDefaultProjectID), expectedDefaultRegion: s(v2ValidDefaultRegion), expectedDefaultZone: s(v2ValidDefaultZone), }, @@ -52,6 +55,7 @@ func TestLoadEnvProfile(t *testing.T) { expectedAccessKey: s(v2ValidAccessKey), expectedSecretKey: s(v2ValidSecretKey), expectedDefaultOrganizationID: s(v2ValidDefaultOrganizationID), + expectedDefaultProjectID: s(v2ValidDefaultProjectID), expectedDefaultRegion: s(v2ValidDefaultRegion), }, { @@ -65,6 +69,7 @@ func TestLoadEnvProfile(t *testing.T) { expectedSecretKey: s(v2ValidSecretKey2), expectedInsecure: b(true), expectedDefaultOrganizationID: s(v2ValidDefaultOrganizationID2), + expectedDefaultProjectID: s(v2ValidDefaultProjectID2), expectedDefaultRegion: s(v2ValidDefaultRegion2), }, } diff --git a/validation/is.go b/validation/is.go index 929d88d81..a7e52d0bd 100644 --- a/validation/is.go +++ b/validation/is.go @@ -34,6 +34,11 @@ func IsOrganizationID(s string) bool { return IsUUID(s) } +// IsProjectID returns true if the given string has a valid Scaleway project ID format. +func IsProjectID(s string) bool { + return IsUUID(s) +} + // IsRegion returns true if the given string has a valid region format. func IsRegion(s string) bool { return isRegionRegex.MatchString(s)