@@ -15,9 +15,7 @@ import (
1515 iamcommands "github.com/scaleway/scaleway-cli/v2/internal/namespaces/iam/v1alpha1"
1616 "github.com/scaleway/scaleway-cli/v2/internal/terminal"
1717 iam "github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1"
18- "github.com/scaleway/scaleway-sdk-go/logger"
1918 "github.com/scaleway/scaleway-sdk-go/scw"
20- "github.com/scaleway/scaleway-sdk-go/validation"
2119)
2220
2321/*
@@ -129,191 +127,80 @@ Default path for configuration file is based on the following priority order:
129127 Command : "scw config --help" ,
130128 },
131129 },
132- PreValidateFunc : func (ctx context.Context , argsI interface {}) error {
130+ Run : func (ctx context.Context , argsI interface {}) ( i interface {}, e error ) {
133131 args := argsI .(* initArgs )
134132
133+ profileName := core .ExtractProfileName (ctx )
134+ configPath := core .ExtractConfigPath (ctx )
135+
135136 // Show logo banner, or simple welcome message
136- if terminal .GetWidth () >= 80 {
137- interactive .Printf ("%s\n %s\n \n " , interactive .Center (logo ), interactive .Line ("-" ))
138- } else {
139- interactive .Printf ("Welcome to the Scaleway Cli\n \n " )
140- }
137+ printScalewayBanner ()
141138
142- config , err := scw .LoadConfigFromPath (core .ExtractConfigPath (ctx ))
143-
144- // If it is not a new config, ask if we want to override the existing config
145- if err == nil && ! config .IsEmpty () {
146- _ , _ = interactive .PrintlnWithoutIndent (`
147- Current config is located at ` + core .ExtractConfigPath (ctx ) + `
148- ` + terminal .Style (fmt .Sprint (config ), color .Faint ) + `
149- ` )
150- overrideConfig , err := interactive .PromptBoolWithConfig (& interactive.PromptBoolConfig {
151- Prompt : "Do you want to override the current config?" ,
152- DefaultValue : true ,
153- Ctx : ctx ,
154- })
155- if err != nil {
156- return err
157- }
158- if ! overrideConfig {
159- return fmt .Errorf ("initialization canceled" )
160- }
139+ err := promptProfileOverride (ctx , configPath , profileName )
140+ if err != nil {
141+ return nil , err
161142 }
162143
163- // Manually prompt for missing args:
164-
165144 // Credentials
166145 if args .SecretKey == "" {
167- _ , _ = interactive .Println ()
168- args .SecretKey , err = promptSecret (ctx )
146+ args .SecretKey , err = promptSecretKey (ctx )
169147 if err != nil {
170- return err
148+ return nil , err
171149 }
172150 }
173151
174152 if args .AccessKey == "" {
175- _ , _ = interactive .Println ()
176153 args .AccessKey , err = promptAccessKey (ctx )
177154 if err != nil {
178- return err
155+ return nil , err
179156 }
180157 }
181158
182159 if args .OrganizationID == "" {
183- _ , _ = interactive .Println ()
184- args .OrganizationID , err = interactive .PromptStringWithConfig (& interactive.PromptStringConfig {
185- Ctx : ctx ,
186- Prompt : "Choose your default organization ID" ,
187- ValidateFunc : func (s string ) error {
188- if ! validation .IsUUID (s ) {
189- return fmt .Errorf ("organization id is not a valid uuid" )
190- }
191- return nil
192- },
193- })
160+ args .OrganizationID , err = promptOrganizationID (ctx )
194161 if err != nil {
195- return err
162+ return nil , err
196163 }
197164 }
198165
199- // Zone
200- if args .Zone == "" {
201- _ , _ = interactive .Println ()
202- zone , err := interactive .PromptStringWithConfig (& interactive.PromptStringConfig {
203- Ctx : ctx ,
204- Prompt : "Select a zone" ,
205- DefaultValueDoc : "fr-par-1" ,
206- DefaultValue : "fr-par-1" ,
207- ValidateFunc : func (s string ) error {
208- logger .Debugf ("s: %v" , s )
209- if ! validation .IsZone (s ) {
210- return fmt .Errorf ("invalid zone" )
211- }
212- return nil
213- },
214- })
166+ if args .ProjectID == "" {
167+ args .ProjectID = getAPIKeyDefaultProjectID (ctx , args .AccessKey , args .SecretKey )
168+ }
169+
170+ if args .ProjectID == "" {
171+ args .ProjectID , err = promptProjectID (ctx )
215172 if err != nil {
216- return err
173+ return nil , err
217174 }
218- args .Zone , err = scw .ParseZone (zone )
175+ }
176+
177+ // Ask for default zone, currently not used as CLI will default to fr-par-1
178+ if args .Zone == "" {
179+ args .Zone , err = promptDefaultZone (ctx )
219180 if err != nil {
220- return err
181+ return nil , err
221182 }
222183 }
223184
224185 // Deduce Region from Zone
225186 if args .Region == "" {
226187 args .Region , err = args .Zone .Region ()
227188 if err != nil {
228- return err
189+ return nil , err
229190 }
230191 }
231192
232193 // Ask for send usage permission
233194 if args .SendTelemetry == nil {
234- _ , _ = interactive .Println ()
235- _ , _ = interactive .PrintlnWithoutIndent (`
236- To improve this tool we rely on diagnostic and usage data.
237- Sending such data is optional and can be disabled at any time by running "scw config set send-telemetry=false".
238- ` )
239-
240- sendTelemetry , err := interactive .PromptBoolWithConfig (& interactive.PromptBoolConfig {
241- Prompt : "Do you want to send usage statistics and diagnostics?" ,
242- DefaultValue : true ,
243- Ctx : ctx ,
244- })
195+ args .SendTelemetry , err = promptTelemetry (ctx )
245196 if err != nil {
246- return err
197+ return nil , err
247198 }
248-
249- args .SendTelemetry = scw .BoolPtr (sendTelemetry )
250199 }
251200
252201 // Ask whether we should install autocomplete
253202 if args .InstallAutocomplete == nil {
254- _ , _ = interactive .Println ()
255- _ , _ = interactive .PrintlnWithoutIndent (`
256- To fully enjoy Scaleway CLI we recommend you install autocomplete support in your shell.
257- ` )
258-
259- installAutocomplete , err := interactive .PromptBoolWithConfig (& interactive.PromptBoolConfig {
260- Ctx : ctx ,
261- Prompt : "Do you want to install autocomplete?" ,
262- DefaultValue : true ,
263- })
264- if err != nil {
265- return err
266- }
267-
268- args .InstallAutocomplete = scw .BoolPtr (installAutocomplete )
269- }
270-
271- return nil
272- },
273- Run : func (ctx context.Context , argsI interface {}) (i interface {}, e error ) {
274- args := argsI .(* initArgs )
275- // Check if a config exists
276- // Creates a new one if it does not
277- configPath := core .ExtractConfigPath (ctx )
278- config , err := scw .LoadConfigFromPath (configPath )
279- if err != nil {
280- _ , ok := err .(* scw.ConfigFileNotFoundError )
281- if ok {
282- config = & scw.Config {}
283- interactive .Printf ("Creating new config at %s\n " , configPath )
284- } else {
285- return nil , err
286- }
287- }
288-
289- if args .SendTelemetry != nil {
290- config .SendTelemetry = args .SendTelemetry
291- }
292-
293- client := core .ExtractClient (ctx )
294- api := iam .NewAPI (client )
295-
296- apiKey , err := api .GetAPIKey (& iam.GetAPIKeyRequest {AccessKey : args .AccessKey }, scw .WithAuthRequest (args .AccessKey , args .SecretKey ))
297- if err != nil && ! is403Error (err ) {
298- // If 403 Unauthorized, API Key does not have permissions to get himself
299- return nil , err
300- }
301-
302- if apiKey != nil && args .ProjectID == "" {
303- args .ProjectID = apiKey .DefaultProjectID
304- }
305-
306- if args .ProjectID == "" {
307- args .ProjectID , err = interactive .PromptStringWithConfig (& interactive.PromptStringConfig {
308- Ctx : ctx ,
309- Prompt : "Default project ID" ,
310- ValidateFunc : func (s string ) error {
311- if ! validation .IsUUID (s ) {
312- return fmt .Errorf ("given project ID is not a valid UUID" )
313- }
314- return nil
315- },
316- })
203+ args .InstallAutocomplete , err = promptAutocomplete (ctx )
317204 if err != nil {
318205 return nil , err
319206 }
@@ -328,8 +215,12 @@ Default path for configuration file is based on the following priority order:
328215 DefaultProjectID : & args .ProjectID , // An API key is always bound to a project.
329216 }
330217
218+ config , err := loadConfigOrEmpty (configPath )
219+ if err != nil {
220+ return nil , err
221+ }
222+
331223 // Save the profile as default or as a named profile
332- profileName := core .ExtractProfileName (ctx )
333224 if profileName == scw .DefaultProfileName {
334225 // Default configuration
335226 config .Profile = * profile
@@ -382,67 +273,48 @@ Default path for configuration file is based on the following priority order:
382273 }
383274}
384275
385- func promptSecret (ctx context.Context ) (string , error ) {
386- secret , err := interactive .Readline (& interactive.ReadlineConfig {
387- Ctx : ctx ,
388- PromptFunc : func (value string ) string {
389- secretKey := "secret-key"
390- switch {
391- case validation .IsUUID (value ):
392- secretKey = terminal .Style (secretKey , color .FgBlue )
393- }
394- return terminal .Style (fmt .Sprintf ("Enter a valid %s: " , secretKey ), color .Bold )
395- },
396- ValidateFunc : func (s string ) error {
397- if validation .IsSecretKey (s ) {
398- return nil
399- }
400- return fmt .Errorf ("invalid secret-key" )
401- },
402- })
403- if err != nil {
404- return "" , err
405- }
406-
407- switch {
408- case validation .IsUUID (secret ):
409- return secret , nil
410-
411- default :
412- return "" , fmt .Errorf ("invalid secret-key: '%v'" , secret )
276+ func printScalewayBanner () {
277+ if terminal .GetWidth () >= 80 {
278+ interactive .Printf ("%s\n %s\n \n " , interactive .Center (logo ), interactive .Line ("-" ))
279+ } else {
280+ interactive .Printf ("Welcome to the Scaleway Cli\n \n " )
413281 }
414282}
415283
416- func promptAccessKey (ctx context.Context ) (string , error ) {
417- key , err := interactive .Readline (& interactive.ReadlineConfig {
418- Ctx : ctx ,
419- PromptFunc : func (value string ) string {
420- accessKey := "access-key"
421- switch {
422- case validation .IsAccessKey (value ):
423- accessKey = terminal .Style (accessKey , color .FgBlue )
424- }
425- return terminal .Style (fmt .Sprintf ("Enter a valid %s: " , accessKey ), color .Bold )
426- },
427- ValidateFunc : func (s string ) error {
428- if ! validation .IsAccessKey (s ) {
429- return fmt .Errorf ("invalid access-key" )
430- }
431-
432- return nil
433- },
434- })
284+ // loadConfigOrEmpty checks if a config exists
285+ // Creates a new one if it does not
286+ func loadConfigOrEmpty (configPath string ) (* scw.Config , error ) {
287+ config , err := scw .LoadConfigFromPath (configPath )
435288 if err != nil {
436- return "" , err
289+ _ , ok := err .(* scw.ConfigFileNotFoundError )
290+ if ok {
291+ config = & scw.Config {}
292+ interactive .Printf ("Creating new config\n " )
293+ } else {
294+ return nil , err
295+ }
437296 }
297+ return config , nil
298+ }
438299
439- switch {
440- case validation .IsAccessKey (key ):
441- return key , nil
300+ // getAPIKeyDefaultProjectID tries to find the api-key default project ID
301+ // return an empty string if it cannot find it
302+ func getAPIKeyDefaultProjectID (ctx context.Context , accessKey string , secretKey string ) string {
303+ client := core .ExtractClient (ctx )
304+ api := iam .NewAPI (client )
305+
306+ apiKey , err := api .GetAPIKey (& iam.GetAPIKeyRequest {AccessKey : accessKey }, scw .WithAuthRequest (accessKey , secretKey ))
307+ if err != nil && ! is403Error (err ) {
308+ // If 403 Unauthorized, API Key does not have permissions to get himself
309+ // It requires IAM permission to fetch an API Key
310+ return ""
311+ }
442312
443- default :
444- return "" , fmt . Errorf ( "invalid access-key: '%v'" , key )
313+ if apiKey == nil {
314+ return ""
445315 }
316+
317+ return apiKey .DefaultProjectID
446318}
447319
448320// isHTTPCodeError returns true if err is an http error with code statusCode
0 commit comments