Repository: radius-project/radius

Issue:
Update resource group prompt message
## Bug information

### Steps to reproduce (required)
Run through rad init command. Select configure cloud provider for azure workflow.

### Observed behavior (required)
Prompts for creating new resource group with "Create a new Resource Group [Y/n]?"

### Desired behavior (required)
Prompt message should be "Create a new Resource Group?" (without the yes no at the end)

PS: rad env init kubernetes has to be deprecated along with this.

[AB#5661](https://dev.azure.com/azure-octo/e61041b4-555f-47ae-95b2-4f8ab480ea57/_workitems/edit/5661)

[AB#16226](https://dev.azure.com/azure-octo/e61041b4-555f-47ae-95b2-4f8ab480ea57/_workitems/edit/16226)

Discussion:
@AaronCrawfis: We should also make sure we remove any extra `:` characters:

<img width="571" alt="image" src="https://user-images.githubusercontent.com/54363786/211678726-c4f5d954-087d-4907-99b6-ec8fbba4166a.png">
@willtsai: it looks like the prompt has a list of options to select "yes" or "no" and is not freeform. Thus, I'm not going to tag this bug for `public-release` 

```
Create a new Resource Group? [Y/n]            
  > 1. No                                     
    2. Yes 
```@AaronCrawfis: > it looks like the prompt has a list of options to select "yes" or "no" and is not freeform. Thus, I'm not going to tag this bug for `public-release`
> 
> ```
> Create a new Resource Group? [Y/n]            
>   > 1. No                                     
>     2. Yes 
> ```

@willtsai I'd love if we could keep this one in for public release. It's a tiny fix that will improve our fit, finish, and polish@nicolejms: very old so closing

Solution patch:
diff --git a/pkg/cli/cmd/provider/common/validation.go b/pkg/cli/cmd/provider/common/validation.go
index ee493f73c..8d385cb77 100644
--- a/pkg/cli/cmd/provider/common/validation.go
+++ b/pkg/cli/cmd/provider/common/validation.go
@@ -116,7 +116,7 @@ func SelectEnvironmentName(cmd *cobra.Command, defaultVal string, interactive bo
 			return "", err
 		}
 		if envStr == "" {
-			return "", fmt.Errorf("environmentName cannot be empty")
+			return defaultVal, nil
 		}
 	} else {
 		if envStr == "" {
@@ -142,7 +142,7 @@ func SelectNamespace(cmd *cobra.Command, defaultVal string, interactive bool, pr
 			return "", err
 		}
 		if val == "" {
-			return "", fmt.Errorf("Namespace cannot be empty")
+			return defaultVal, nil
 		}
 	} else {
 		val, _ = cmd.Flags().GetString("namespace")
diff --git a/pkg/cli/cmd/radInit/init.go b/pkg/cli/cmd/radInit/init.go
index 7e63710e5..39d9c1417 100644
--- a/pkg/cli/cmd/radInit/init.go
+++ b/pkg/cli/cmd/radInit/init.go
@@ -35,9 +35,9 @@ import (
 )
 
 const (
-	AzureCloudProvider = "Azure"
-	AWSCloudProvider   = "AWS"
-	BackNavigator      = "[back]"
+	azureCloudProvider = "Azure"
+	awsCloudProvider   = "AWS"
+	backNavigator      = "[back]"
 
 	confirmCloudProviderPrompt    = "Add cloud providers for cloud resources?"
 	confirmReinstallRadiusPrompt  = "Would you like to reinstall Radius control plane and configure cloud providers?"
@@ -45,7 +45,6 @@ const (
 	enterApplicationName          = "Choose an application name"
 	selectKubeContextPrompt       = "Select the kubeconfig context to install Radius into"
 	selectCloudProviderPrompt     = "Select your cloud provider"
-	defaultKubeContext            = "default"
 	kubernetesKind                = "kubernetes"
 )
 
@@ -271,13 +270,15 @@ func (r *Runner) Validate(cmd *cobra.Command, args []string) error {
 					return &cli.FriendlyError{Message: "Error reading cloud provider"}
 				}
 				switch cloudProvider {
-				case AzureCloudProvider:
+				case azureCloudProvider:
 					r.AzureCloudProvider, err = r.SetupInterface.ParseAzureProviderArgs(cmd, true, r.Prompter)
 					if err != nil {
 						return err
 					}
-				case AWSCloudProvider:
+				case awsCloudProvider:
 					r.Output.LogInfo("AWS is not supported")
+				case backNavigator:
+					break
 				default:
 					return &cli.FriendlyError{Message: "Unsupported Cloud Provider"}
 				}
@@ -425,7 +426,6 @@ func selectKubeContext(currentContext string, kubeContexts map[string]*api.Conte
 	values := []string{}
 	if interactive {
 		// Ensure current context is at the top as the default
-		values = append(values, defaultKubeContext)
 		values = append(values, currentContext)
 		for k := range kubeContexts {
 			if k != currentContext {
@@ -436,10 +436,6 @@ func selectKubeContext(currentContext string, kubeContexts map[string]*api.Conte
 		if err != nil {
 			return "", err
 		}
-		// if default is selected return currentContext as the value is appended with (current)
-		if strings.EqualFold(kubeContext, defaultKubeContext) {
-			return currentContext, nil
-		}
 		return kubeContext, nil
 	}
 
@@ -448,7 +444,7 @@ func selectKubeContext(currentContext string, kubeContexts map[string]*api.Conte
 
 // Selects the cloud provider, returns -1 if back and -2 if not supported
 func selectCloudProvider(prompter prompt.Interface) (string, error) {
-	values := []string{AzureCloudProvider, AWSCloudProvider, BackNavigator}
+	values := []string{azureCloudProvider, awsCloudProvider, backNavigator}
 	return prompter.GetListInput(values, selectCloudProviderPrompt)
 }
 
diff --git a/pkg/cli/prompt/list/list.go b/pkg/cli/prompt/list/list.go
index 5c9c1270b..8e2e1e2f7 100644
--- a/pkg/cli/prompt/list/list.go
+++ b/pkg/cli/prompt/list/list.go
@@ -8,6 +8,7 @@ package list
 import (
 	"fmt"
 	"io"
+	"os"
 
 	"github.com/charmbracelet/bubbles/list"
 	tea "github.com/charmbracelet/bubbletea"
@@ -27,7 +28,7 @@ var (
 
 type item string
 
-func (i item) FilterValue() string { return "" }
+func (i item) FilterValue() string { return string(i) }
 
 type itemHandler struct{}
 
@@ -75,6 +76,7 @@ func NewListModel(choices []string, promptMsg string) ListModel {
 	l := list.New(items, itemHandler{}, defaultWidth, listHeight)
 	l.Title = promptMsg
 	l.SetShowStatusBar(false)
+	l.SetFilteringEnabled(true)
 	l.Styles.Title = titleStyle
 
 	return ListModel{
@@ -100,16 +102,16 @@ func (m ListModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 
 	case tea.KeyMsg:
 		switch keypress := msg.String(); keypress {
-		case "ctrl+c":
-			m.Quitting = true
-			return m, tea.Quit
-
+		case "ctrl+c", "esc", "q":
+			os.Exit(1)
 		case "enter":
-			i, ok := m.List.SelectedItem().(item)
-			if ok {
-				m.Choice = string(i)
+			if m.List.FilterState() != list.Filtering {
+				i, ok := m.List.SelectedItem().(item)
+				if ok {
+					m.Choice = string(i)
+				}
+				return m, tea.Quit
 			}
-			return m, tea.Quit
 		}
 	}
 
@@ -121,11 +123,7 @@ func (m ListModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 // View renders the view after user selection.
 func (m ListModel) View() string {
 	if m.Choice != "" {
-		return quitTextStyle.Render(fmt.Sprintf("Selected Value: %s", m.Choice))
-	}
-
-	if m.Quitting {
-		return quitTextStyle.Render("Quitting...")
+		return quitTextStyle.Render(fmt.Sprintf("%s: %s", m.List.Title, m.Choice))
 	}
 
 	return "\n" + m.List.View()
diff --git a/pkg/cli/prompt/prompt.go b/pkg/cli/prompt/prompt.go
index 77f98823e..b5a0514b1 100644
--- a/pkg/cli/prompt/prompt.go
+++ b/pkg/cli/prompt/prompt.go
@@ -183,7 +183,6 @@ func (i *Impl) GetTextInput(promptMsg string, defaultPlaceHolder string) (string
 	if !ok {
 		return "", &ErrUnsupportedModel{}
 	}
-	fmt.Println("Entered value:", tm.GetValue())
 
 	return tm.GetValue(), nil
 }
diff --git a/pkg/cli/prompt/text/text.go b/pkg/cli/prompt/text/text.go
index 21ed52161..18a299ef3 100644
--- a/pkg/cli/prompt/text/text.go
+++ b/pkg/cli/prompt/text/text.go
@@ -7,20 +7,27 @@ package text
 
 import (
 	"fmt"
+	"os"
 
 	"github.com/charmbracelet/bubbles/textinput"
 	tea "github.com/charmbracelet/bubbletea"
+	"github.com/charmbracelet/lipgloss"
 )
 
 type (
 	errMsg error
 )
 
+var (
+	QuitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4)
+)
+
 // Model is text model for bubble tea.
 type Model struct {
-	textInput textinput.Model
-	promptMsg string
-	err       error
+	textInput    textinput.Model
+	promptMsg    string
+	valueEntered bool
+	err          error
 }
 
 // NewTextModel returns a new text model with prompt message.
@@ -31,9 +38,10 @@ func NewTextModel(promptMsg string, placeHolder string) Model {
 	ti.Width = 20
 
 	return Model{
-		textInput: ti,
-		promptMsg: promptMsg,
-		err:       nil,
+		textInput:    ti,
+		promptMsg:    promptMsg,
+		valueEntered: false,
+		err:          nil,
 	}
 }
 
@@ -49,8 +57,11 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	switch msg := msg.(type) {
 	case tea.KeyMsg:
 		switch msg.Type {
-		case tea.KeyEnter, tea.KeyCtrlC, tea.KeyEsc:
+		case tea.KeyEnter:
+			m.valueEntered = true
 			return m, tea.Quit
+		case tea.KeyCtrlC, tea.KeyEsc:
+			os.Exit(1)
 		}
 
 	// We handle errors just like any other message
@@ -64,6 +75,14 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 
 // View renders a view with user selected value.
 func (m Model) View() string {
+	if m.valueEntered {
+		if m.textInput.Value() == "" {
+			return QuitTextStyle.Render(fmt.Sprintf("%s: %s", m.promptMsg, m.textInput.Placeholder))
+		} else {
+			return QuitTextStyle.Render(fmt.Sprintf("%s: %s", m.promptMsg, m.textInput.Value()))
+		}
+
+	}
 	return fmt.Sprintf("%s\n\n%s\n\n%s", m.promptMsg, m.textInput.View(), "(esc to quit)")
 }
 
diff --git a/pkg/cli/setup/azure.go b/pkg/cli/setup/azure.go
index d6f481372..9c6cca49d 100644
--- a/pkg/cli/setup/azure.go
+++ b/pkg/cli/setup/azure.go
@@ -69,7 +69,7 @@ func parseAzureProviderInteractive(cmd *cobra.Command, prompter prompt.Interface
 	}
 
 	clientID, err := prompter.GetTextInput(
-		"Enter the `appId` of the service principal used to create Azure resources:",
+		"Enter the `appId` of the service principal used to create Azure resources",
 		"Enter AppId...",
 	)
 	if err != nil {
@@ -81,7 +81,7 @@ func parseAzureProviderInteractive(cmd *cobra.Command, prompter prompt.Interface
 	}
 
 	clientSecret, err := prompter.GetTextInput(
-		"Enter the `password` of the service principal used to create Azure resources:",
+		"Enter the `password` of the service principal used to create Azure resources",
 		"",
 	)
 	if err != nil {
@@ -92,7 +92,7 @@ func parseAzureProviderInteractive(cmd *cobra.Command, prompter prompt.Interface
 	}
 
 	tenantID, err := prompter.GetTextInput(
-		"Enter the `tenant` of the service principal used to create Azure resources:",
+		"Enter the `tenant` of the service principal used to create Azure resources",
 		"",
 	)
 	if err != nil {
@@ -246,7 +246,7 @@ func selectSubscription(ctx context.Context, prompter prompt.Interface, armConfi
 		names = append(names, s.DisplayName)
 	}
 
-	name, err := prompter.GetListInput(names, "Select Subscription:")
+	name, err := prompter.GetListInput(names, "Select Subscription")
 	if err != nil {
 		return azure.Subscription{}, err
 	}
@@ -386,7 +386,7 @@ func promptUserForRgName(ctx context.Context, client *armresources.ResourceGroup
 		if yes {
 			return defaultRgName, nil
 		}
-		name, err = prompter.GetListInput(names, "Select ResourceGroup:")
+		name, err = prompter.GetListInput(names, "Select ResourceGroup")
 		if err != nil {
 			return "", err
 		}



Test patch:
diff --git a/pkg/cli/cmd/provider/common/validation_test.go b/pkg/cli/cmd/provider/common/validation_test.go
new file mode 100644
index 000000000..50f235e95
--- /dev/null
+++ b/pkg/cli/cmd/provider/common/validation_test.go
@@ -0,0 +1,111 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+// ------------------------------------------------------------
+
+package common
+
+import (
+	"errors"
+	"fmt"
+	"testing"
+
+	"github.com/golang/mock/gomock"
+	"github.com/project-radius/radius/pkg/cli/prompt"
+	"github.com/spf13/cobra"
+	"github.com/stretchr/testify/require"
+)
+
+const (
+	defaultTestValue = "default"
+	testEnvName      = "env0"
+	invalidEnvName   = "&^*env"
+)
+
+func Test_Environment_Selection(t *testing.T) {
+	testCmd := &cobra.Command{}
+	testCmd.Flags().String("environment", testEnvName, "Environment flag")
+	testEmptyEnvCmd := &cobra.Command{}
+	testEmptyEnvCmd.Flags().String("environment", "", "Environment flag")
+	testInvalidEnvNameCmd := &cobra.Command{}
+	testInvalidEnvNameCmd.Flags().String("environment", invalidEnvName, "Environment flag")
+
+	tests := []struct {
+		name        string
+		cmd         *cobra.Command
+		defaultVal  string
+		interactive bool
+		expectedEnv string
+		err         error
+		mockSetup   func(*prompt.MockInterface)
+	}{
+		{
+			name:        "Select environment non interactive",
+			cmd:         testCmd,
+			defaultVal:  defaultTestValue,
+			interactive: false,
+			expectedEnv: testEnvName,
+		},
+		{
+			name:        "Default environment non interactive",
+			cmd:         testEmptyEnvCmd,
+			defaultVal:  defaultTestValue,
+			interactive: false,
+			expectedEnv: defaultTestValue,
+		},
+		{
+			name:        "Undefined environment flag non interactive",
+			cmd:         &cobra.Command{},
+			defaultVal:  defaultTestValue,
+			interactive: false,
+			err:         errors.New("flag accessed but not defined: environment"),
+		},
+		{
+			name:        "Invalid environment name non interactive",
+			cmd:         testInvalidEnvNameCmd,
+			defaultVal:  invalidEnvName,
+			interactive: false,
+			err:         fmt.Errorf("%s %s. Use --environment option to specify the valid name", invalidEnvName, prompt.InvalidResourceNameMessage),
+		},
+		{
+			name:        "environment name interactive",
+			cmd:         testEmptyEnvCmd,
+			defaultVal:  defaultTestValue,
+			interactive: true,
+			expectedEnv: testEnvName,
+			err:         nil,
+			mockSetup:   func(m *prompt.MockInterface) {setupEnvNameTextPrompt(m, testEnvName)},
+		},
+		{
+			name:        "environment name interactive",
+			cmd:         testEmptyEnvCmd,
+			defaultVal:  defaultTestValue,
+			interactive: true,
+			expectedEnv: defaultTestValue,
+			err:         nil,
+			mockSetup:   func(m *prompt.MockInterface) {setupEnvNameTextPrompt(m, "")},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ctrl := gomock.NewController(t)
+			prompter := prompt.NewMockInterface(ctrl)
+			if tt.mockSetup != nil {
+				tt.mockSetup(prompter)
+			}
+			selected, err := SelectEnvironmentName(tt.cmd, tt.defaultVal, tt.interactive, prompter)
+			if tt.err != nil {
+				require.Equal(t, err, tt.err)
+			} else {
+				require.NoError(t, err)
+				require.Equal(t, selected, tt.expectedEnv)
+			}
+		})
+	}
+}
+
+func setupEnvNameTextPrompt(prompter *prompt.MockInterface, value string) {
+	prompter.EXPECT().
+		GetTextInput(EnterEnvironmentNamePrompt, gomock.Any()).
+		Return(value, nil).Times(1)
+}
diff --git a/pkg/cli/cmd/radInit/init_test.go b/pkg/cli/cmd/radInit/init_test.go
index 6f4322a1d..1eede5b6f 100644
--- a/pkg/cli/cmd/radInit/init_test.go
+++ b/pkg/cli/cmd/radInit/init_test.go
@@ -187,7 +187,7 @@ func Test_Validate(t *testing.T) {
 
 				// Add azure provider
 				initAddCloudProviderPromptYes(mocks.Prompter)
-				initSelectCloudProvider(mocks.Prompter)
+				initSelectCloudProvider(mocks.Prompter, "Azure")
 				initParseCloudProvider(mocks.Setup, mocks.Prompter)
 
 				// Don't add any other cloud providers
@@ -391,6 +391,36 @@ func Test_Validate(t *testing.T) {
 				initNamespacePromptError(mocks.Prompter)
 			},
 		},
+		{
+			Name:          "Init Command Navigate back while configuring cloud provider",
+			Input:         []string{},
+			ExpectedValid: true,
+			ConfigHolder: framework.ConfigHolder{
+				ConfigFilePath: "",
+				Config:         config,
+			},
+			ConfigureMocks: func(mocks radcli.ValidateMocks) {
+				// Radius is already installed, no reinstall
+				initGetKubeContextSuccess(mocks.Kubernetes)
+				initKubeContextWithKind(mocks.Prompter)
+				initHelmMockRadiusInstalled(mocks.Helm)
+				// Reinstall radius to configure cloud provider
+				initRadiusReinstallYes(mocks.Prompter)
+				initAddCloudProviderPromptYes(mocks.Prompter)
+				// No existing environment, users will be prompted to create a new one
+				setExistingEnvironments(mocks.ApplicationManagementClient, []corerp.EnvironmentResource{})
+
+				// Choose default name and namespace
+				initEnvNamePrompt(mocks.Prompter)
+				initNamespacePrompt(mocks.Prompter)
+				// Oops! I don't need to add cloud provider, navigate back to reinstall prompt
+				initSelectCloudProvider(mocks.Prompter, "[back]")
+				initAddCloudProviderPromptNo(mocks.Prompter)
+
+				// No application
+				setScaffoldApplicationPromptNo(mocks.Prompter)
+			},
+		},
 	}
 	radcli.SharedValidateValidation(t, NewCommand, testcases)
 }
@@ -669,10 +699,10 @@ func initAddCloudProviderPromptYes(prompter *prompt.MockInterface) {
 		Return("Yes", nil).Times(1)
 }
 
-func initSelectCloudProvider(prompter *prompt.MockInterface) {
+func initSelectCloudProvider(prompter *prompt.MockInterface, value string) {
 	prompter.EXPECT().
 		GetListInput(gomock.Any(), selectCloudProviderPrompt).
-		Return("Azure", nil).Times(1)
+		Return(value, nil).Times(1)
 }
 
 func initHelmMockRadiusInstalled(helmMock *helm.MockInterface) {