@@ -15,9 +15,7 @@ import (
15
15
iamcommands "github.com/scaleway/scaleway-cli/v2/internal/namespaces/iam/v1alpha1"
16
16
"github.com/scaleway/scaleway-cli/v2/internal/terminal"
17
17
iam "github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1"
18
- "github.com/scaleway/scaleway-sdk-go/logger"
19
18
"github.com/scaleway/scaleway-sdk-go/scw"
20
- "github.com/scaleway/scaleway-sdk-go/validation"
21
19
)
22
20
23
21
/*
@@ -129,191 +127,80 @@ Default path for configuration file is based on the following priority order:
129
127
Command : "scw config --help" ,
130
128
},
131
129
},
132
- PreValidateFunc : func (ctx context.Context , argsI interface {}) error {
130
+ Run : func (ctx context.Context , argsI interface {}) ( i interface {}, e error ) {
133
131
args := argsI .(* initArgs )
134
132
133
+ profileName := core .ExtractProfileName (ctx )
134
+ configPath := core .ExtractConfigPath (ctx )
135
+
135
136
// 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 ()
141
138
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
161
142
}
162
143
163
- // Manually prompt for missing args:
164
-
165
144
// Credentials
166
145
if args .SecretKey == "" {
167
- _ , _ = interactive .Println ()
168
- args .SecretKey , err = promptSecret (ctx )
146
+ args .SecretKey , err = promptSecretKey (ctx )
169
147
if err != nil {
170
- return err
148
+ return nil , err
171
149
}
172
150
}
173
151
174
152
if args .AccessKey == "" {
175
- _ , _ = interactive .Println ()
176
153
args .AccessKey , err = promptAccessKey (ctx )
177
154
if err != nil {
178
- return err
155
+ return nil , err
179
156
}
180
157
}
181
158
182
159
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 )
194
161
if err != nil {
195
- return err
162
+ return nil , err
196
163
}
197
164
}
198
165
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 )
215
172
if err != nil {
216
- return err
173
+ return nil , err
217
174
}
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 )
219
180
if err != nil {
220
- return err
181
+ return nil , err
221
182
}
222
183
}
223
184
224
185
// Deduce Region from Zone
225
186
if args .Region == "" {
226
187
args .Region , err = args .Zone .Region ()
227
188
if err != nil {
228
- return err
189
+ return nil , err
229
190
}
230
191
}
231
192
232
193
// Ask for send usage permission
233
194
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 )
245
196
if err != nil {
246
- return err
197
+ return nil , err
247
198
}
248
-
249
- args .SendTelemetry = scw .BoolPtr (sendTelemetry )
250
199
}
251
200
252
201
// Ask whether we should install autocomplete
253
202
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 )
317
204
if err != nil {
318
205
return nil , err
319
206
}
@@ -328,8 +215,12 @@ Default path for configuration file is based on the following priority order:
328
215
DefaultProjectID : & args .ProjectID , // An API key is always bound to a project.
329
216
}
330
217
218
+ config , err := loadConfigOrEmpty (configPath )
219
+ if err != nil {
220
+ return nil , err
221
+ }
222
+
331
223
// Save the profile as default or as a named profile
332
- profileName := core .ExtractProfileName (ctx )
333
224
if profileName == scw .DefaultProfileName {
334
225
// Default configuration
335
226
config .Profile = * profile
@@ -382,67 +273,48 @@ Default path for configuration file is based on the following priority order:
382
273
}
383
274
}
384
275
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 " )
413
281
}
414
282
}
415
283
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 )
435
288
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
+ }
437
296
}
297
+ return config , nil
298
+ }
438
299
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
+ }
442
312
443
- default :
444
- return "" , fmt . Errorf ( "invalid access-key: '%v'" , key )
313
+ if apiKey == nil {
314
+ return ""
445
315
}
316
+
317
+ return apiKey .DefaultProjectID
446
318
}
447
319
448
320
// isHTTPCodeError returns true if err is an http error with code statusCode
0 commit comments