Repository: scaleway/scaleway-cli (go)

Issue:
init: use default when not in an interactive terminal
Using the docker image, if calling `init` without `project-id`, it will fail as it will try to open the list prompt.
Previously it was using `organization-id` as default, this behavior should be added back when not in a tty.

Pull request description:
fix(init): project ID prompt
use simple prompt if apiKey cannot list projects
use default project ID if not in an interactive environment

It would crash if the api key is an application key without AccountRead permission.

Fix #3193 

Patch:
diff --git a/go.mod b/go.mod
index 91ffb0fb..ba0bcc81 100644
--- a/go.mod
+++ b/go.mod
@@ -29,7 +29,7 @@ require (
 	github.com/spf13/cobra v1.7.0
 	github.com/spf13/pflag v1.0.5
 	github.com/stretchr/testify v1.8.4
-	golang.org/x/term v0.8.0
+	golang.org/x/term v0.9.0
 	golang.org/x/text v0.10.0
 	gopkg.in/yaml.v3 v3.0.1
 )
@@ -156,7 +156,7 @@ require (
 	golang.org/x/mod v0.10.0 // indirect
 	golang.org/x/net v0.10.0 // indirect
 	golang.org/x/sync v0.2.0 // indirect
-	golang.org/x/sys v0.8.0 // indirect
+	golang.org/x/sys v0.9.0 // indirect
 	golang.org/x/time v0.3.0 // indirect
 	golang.org/x/tools v0.9.1 // indirect
 	google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
diff --git a/go.sum b/go.sum
index 079dea7b..0e2e98b3 100644
--- a/go.sum
+++ b/go.sum
@@ -757,16 +757,16 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
-golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
+golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
-golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
-golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
+golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/internal/namespaces/baremetal/v1/baremetal_cli.go b/internal/namespaces/baremetal/v1/baremetal_cli.go
index 6d29774f..df407db1 100644
--- a/internal/namespaces/baremetal/v1/baremetal_cli.go
+++ b/internal/namespaces/baremetal/v1/baremetal_cli.go
@@ -520,7 +520,7 @@ func baremetalServerInstall() *core.Command {
 		},
 		SeeAlsos: []*core.SeeAlso{
 			{
-				Command: "scw account ssh-key list",
+				Command: "scw iam ssh-key list",
 				Short:   "List all SSH keys",
 			},
 			{
diff --git a/internal/namespaces/init/init.go b/internal/namespaces/init/init.go
index 1e8a7526..12c5edec 100644
--- a/internal/namespaces/init/init.go
+++ b/internal/namespaces/init/init.go
@@ -169,7 +169,7 @@ Default path for configuration file is based on the following priority order:
 			}
 
 			if args.ProjectID == "" {
-				args.ProjectID = getAPIKeyDefaultProjectID(ctx, args.AccessKey, args.SecretKey)
+				args.ProjectID = getAPIKeyDefaultProjectID(ctx, args.AccessKey, args.SecretKey, args.OrganizationID)
 				args.ProjectID, err = promptProjectID(ctx, args.AccessKey, args.SecretKey, args.OrganizationID, args.ProjectID)
 				if err != nil {
 					return nil, err
@@ -295,8 +295,8 @@ func loadConfigOrEmpty(configPath string) (*scw.Config, error) {
 }
 
 // getAPIKeyDefaultProjectID tries to find the api-key default project ID
-// return an empty string if it cannot find it
-func getAPIKeyDefaultProjectID(ctx context.Context, accessKey string, secretKey string) string {
+// return default project ID (organization ID) if it cannot find it
+func getAPIKeyDefaultProjectID(ctx context.Context, accessKey string, secretKey string, organizationID string) string {
 	client := core.ExtractClient(ctx)
 	api := iam.NewAPI(client)
 
@@ -304,11 +304,11 @@ func getAPIKeyDefaultProjectID(ctx context.Context, accessKey string, secretKey
 	if err != nil && !is403Error(err) {
 		// If 403 Unauthorized, API Key does not have permissions to get himself
 		// It requires IAM permission to fetch an API Key
-		return ""
+		return organizationID
 	}
 
 	if apiKey == nil {
-		return ""
+		return organizationID
 	}
 
 	return apiKey.DefaultProjectID
diff --git a/internal/namespaces/init/prompt.go b/internal/namespaces/init/prompt.go
index 913283ea..a8f374b4 100644
--- a/internal/namespaces/init/prompt.go
+++ b/internal/namespaces/init/prompt.go
@@ -28,7 +28,31 @@ func promptOrganizationID(ctx context.Context) (string, error) {
 	})
 }
 
+func promptManualProjectID(ctx context.Context, defaultProjectID string) (string, error) {
+	_, _ = interactive.Println()
+	return interactive.PromptStringWithConfig(&interactive.PromptStringConfig{
+		Ctx:             ctx,
+		Prompt:          "Choose your default project ID",
+		DefaultValue:    defaultProjectID,
+		DefaultValueDoc: defaultProjectID,
+		ValidateFunc: func(s string) error {
+			if !validation.IsProjectID(s) {
+				return fmt.Errorf("organization id is not a valid uuid")
+			}
+			return nil
+		},
+	})
+}
+
 func promptProjectID(ctx context.Context, accessKey string, secretKey string, organizationID string, defaultProjectID string) (string, error) {
+	if defaultProjectID == "" {
+		defaultProjectID = organizationID
+	}
+
+	if !interactive.IsInteractive {
+		return defaultProjectID, nil
+	}
+
 	client := core.ExtractClient(ctx)
 	api := account.NewAPI(client)
 
@@ -39,6 +63,10 @@ func promptProjectID(ctx context.Context, accessKey string, secretKey string, or
 		return "", fmt.Errorf("failed to list projects: %w", err)
 	}
 
+	if len(res.Projects) == 0 {
+		return promptManualProjectID(ctx, defaultProjectID)
+	}
+
 	defaultIndex := 0
 
 	projects := make([]string, len(res.Projects))
diff --git a/internal/namespaces/instance/v1/instance_cli.go b/internal/namespaces/instance/v1/instance_cli.go
index 1e0d6619..1a589a7f 100644
--- a/internal/namespaces/instance/v1/instance_cli.go
+++ b/internal/namespaces/instance/v1/instance_cli.go
@@ -1033,7 +1033,7 @@ func instanceImageList() *core.Command {
 		},
 		SeeAlsos: []*core.SeeAlso{
 			{
-				Command: "scw marketplace list images",
+				Command: "scw marketplace image list",
 				Short:   "List marketplace images",
 			},
 		},
diff --git a/internal/namespaces/marketplace/v2/marketplace_cli.go b/internal/namespaces/marketplace/v2/marketplace_cli.go
index fe3fa54a..fa38a6fa 100644
--- a/internal/namespaces/marketplace/v2/marketplace_cli.go
+++ b/internal/namespaces/marketplace/v2/marketplace_cli.go
@@ -162,7 +162,7 @@ func marketplaceImageList() *core.Command {
 		}},
 		SeeAlsos: []*core.SeeAlso{
 			{
-				Command: "scw instance list images",
+				Command: "scw instance image list",
 				Short:   "List all images available in an account",
 			},
 		},


Test patch:
diff --git a/cmd/scw/testdata/test-all-usage-baremetal-server-install-usage.golden b/cmd/scw/testdata/test-all-usage-baremetal-server-install-usage.golden
index 26964727..5db5c5aa 100644
--- a/cmd/scw/testdata/test-all-usage-baremetal-server-install-usage.golden
+++ b/cmd/scw/testdata/test-all-usage-baremetal-server-install-usage.golden
@@ -33,7 +33,7 @@ GLOBAL FLAGS:
 
 SEE ALSO:
   # List all SSH keys
-  scw account ssh-key list
+  scw iam ssh-key list
 
   # List OS (useful to get all OS IDs)
   scw baremetal os list
diff --git a/cmd/scw/testdata/test-all-usage-instance-image-list-usage.golden b/cmd/scw/testdata/test-all-usage-instance-image-list-usage.golden
index f6715b85..027eb0e3 100644
--- a/cmd/scw/testdata/test-all-usage-instance-image-list-usage.golden
+++ b/cmd/scw/testdata/test-all-usage-instance-image-list-usage.golden
@@ -29,4 +29,4 @@ GLOBAL FLAGS:
 
 SEE ALSO:
   # List marketplace images
-  scw marketplace list images
+  scw marketplace image list
diff --git a/cmd/scw/testdata/test-all-usage-marketplace-image-list-usage.golden b/cmd/scw/testdata/test-all-usage-marketplace-image-list-usage.golden
index 001dd50e..b6a264f4 100644
--- a/cmd/scw/testdata/test-all-usage-marketplace-image-list-usage.golden
+++ b/cmd/scw/testdata/test-all-usage-marketplace-image-list-usage.golden
@@ -22,4 +22,4 @@ GLOBAL FLAGS:
 
 SEE ALSO:
   # List all images available in an account
-  scw instance list images
+  scw instance image list