Skip to content

Commit

Permalink
Fix interactive and workspace rad init bugs
Browse files Browse the repository at this point in the history
- prompt user for re-install of radius and handle the scenarios
- Create or update radius workspace instead of require existing workspace
issues: #3823 , #3825
  • Loading branch information
Bharath Joginapally authored and Bharath Joginapally committed Sep 29, 2022
1 parent cb2fe88 commit 1abab86
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 167 deletions.
14 changes: 4 additions & 10 deletions cmd/rad/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
resource_delete "github.com/project-radius/radius/pkg/cli/cmd/resource/delete"
resource_list "github.com/project-radius/radius/pkg/cli/cmd/resource/list"
resource_show "github.com/project-radius/radius/pkg/cli/cmd/resource/show"
"github.com/project-radius/radius/pkg/cli/configFile"
"github.com/project-radius/radius/pkg/cli/connections"
"github.com/project-radius/radius/pkg/cli/framework"
"github.com/project-radius/radius/pkg/cli/helm"
Expand All @@ -43,7 +42,7 @@ var RootCmd = &cobra.Command{
}

var resourceCmd = NewResourceCommand()
var ConfigHolderKey = NewContextKey("config")
var ConfigHolderKey = framework.NewContextKey("config")
var ConfigHolder = &framework.ConfigHolder{}

func prettyPrintRPError(err error) string {
Expand Down Expand Up @@ -102,7 +101,7 @@ func initSubCommands() {
Writer: RootCmd.OutOrStdout(),
},
Prompter: &prompt.Impl{},
ConfigFileInterface: &configFile.Impl{},
ConfigFileInterface: &framework.ConfigFileInterfaceImpl{},
KubernetesInterface: &kubernetes.Impl{},
HelmInterface: &helm.Impl{},
}
Expand Down Expand Up @@ -141,14 +140,9 @@ func initConfig() {
ConfigHolder.Config = v
}

type contextKey string

func NewContextKey(purpose string) contextKey {
return contextKey("radius context " + purpose)
}

//TODO: Deprecate once all the commands are moved to new framework
func ConfigFromContext(ctx context.Context) *viper.Viper {
holder := ctx.Value(NewContextKey("config")).(*framework.ConfigHolder)
holder := ctx.Value(framework.NewContextKey("config")).(*framework.ConfigHolder)
if holder == nil {
return nil
}
Expand Down
19 changes: 0 additions & 19 deletions pkg/cli/cmd/provider/common/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,15 @@
package common

import (
"context"
"errors"
"fmt"
"strings"

"github.com/manifoldco/promptui"
"github.com/project-radius/radius/pkg/cli"
"github.com/project-radius/radius/pkg/cli/framework"
"github.com/project-radius/radius/pkg/cli/output"
"github.com/project-radius/radius/pkg/cli/prompt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

func ValidateCloudProviderName(name string) error {
Expand Down Expand Up @@ -89,19 +86,3 @@ func SelectNamespace(cmd *cobra.Command, defaultVal string, interactive bool, pr
}
return val, nil
}

type contextKey string

func NewContextKey(purpose string) contextKey {
return contextKey("radius context " + purpose)
}

// Fetches radius config from the viper context
func ConfigFromContext(ctx context.Context) *viper.Viper {
holder := ctx.Value(NewContextKey("config")).(*framework.ConfigHolder)
if holder == nil {
return nil
}

return holder.Config
}
47 changes: 29 additions & 18 deletions pkg/cli/cmd/radInit/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
"github.com/project-radius/radius/pkg/cli/azure"
"github.com/project-radius/radius/pkg/cli/cmd/commonflags"
"github.com/project-radius/radius/pkg/cli/cmd/provider/common"
"github.com/project-radius/radius/pkg/cli/configFile"
"github.com/project-radius/radius/pkg/cli/connections"
"github.com/project-radius/radius/pkg/cli/framework"
"github.com/project-radius/radius/pkg/cli/helm"
Expand All @@ -34,6 +33,10 @@ const (
AWS
)

const (
kubernetesKind = "kubernetes"
)

func NewCommand(factory framework.Factory) (*cobra.Command, framework.Runner) {
runner := NewRunner(factory)

Expand Down Expand Up @@ -69,7 +72,7 @@ type Runner struct {
RadiusInstalled bool
Reinstall bool
Prompter prompt.Interface
ConfigFileInterface configFile.Interface
ConfigFileInterface framework.ConfigFileInterface
KubernetesInterface kubernetes.Interface
HelmInterface helm.Interface
}
Expand All @@ -88,25 +91,18 @@ func NewRunner(factory framework.Factory) *Runner {

// Validates the user prompts, values provided and builds the picture for the backend to execute
func (r *Runner) Validate(cmd *cobra.Command, args []string) error {
// Validate command line args and
workspace, err := cli.RequireWorkspace(cmd, r.ConfigHolder.Config)
if err != nil {
return &cli.FriendlyError{Message: "Workspace not specified"}
}
r.Workspace = workspace

format, err := cli.RequireOutput(cmd)
if err != nil {
return &cli.FriendlyError{Message: "Output format not specified"}
}
r.Format = format

kubeContext, err := r.KubernetesInterface.GetKubeContext()
kubeContextList, err := r.KubernetesInterface.GetKubeContext()
if err != nil {
return &cli.FriendlyError{Message: "Failed to read kube config"}
}

r.KubeContext, err = selectKubeContext(kubeContext.CurrentContext, kubeContext.Contexts, true, r.Prompter)
r.KubeContext, err = selectKubeContext(kubeContextList.CurrentContext, kubeContextList.Contexts, true, r.Prompter)
if err != nil {
return &cli.FriendlyError{Message: "KubeContext not specified"}
}
Expand All @@ -122,7 +118,7 @@ func (r *Runner) Validate(cmd *cobra.Command, args []string) error {
if err != nil {
return &cli.FriendlyError{Message: "Unable to read reinstall prompt"}
}
if strings.ToLower(y)=="y" {
if strings.ToLower(y) == "y" {
r.Reinstall = true
}
}
Expand All @@ -137,10 +133,20 @@ func (r *Runner) Validate(cmd *cobra.Command, args []string) error {
return &cli.FriendlyError{Message: "Namespace not specified"}
}

r.Workspace = &workspaces.Workspace{
Name: r.KubeContext,
Connection: map[string]interface{}{
"context": r.KubeContext,
"kind": kubernetesKind, // we support only kubernetes for now
},
Environment: r.EnvName,
Scope: fmt.Sprintf("/planes/radius/local/resourceGroups/%s", r.EnvName),
}

// This loop is required for adding multiple cloud providers
// addingAnotherProvider tracks whether a user wants to add multiple cloud provider or not at the time of prompt
addingAnotherProvider := "y"
for strings.ToLower(addingAnotherProvider) == "y" {
for strings.ToLower(addingAnotherProvider) == "y" && r.Reinstall {
var cloudProvider int
// This loop is required to move up a level when the user selects [back] as an option
// addingCloudProvider tracks whether a user wants to add a new cloud provider at the time of prompt
Expand Down Expand Up @@ -191,11 +197,13 @@ func (r *Runner) Validate(cmd *cobra.Command, args []string) error {

// Creates radius resources, azure resources if required based on the user input, command flags
func (r *Runner) Run(ctx context.Context) error {

config := r.ConfigFileInterface.ConfigFromContext(ctx)
//TODO: Initialize cloud providers separately once providers commands are in
// If the user prompts for re-install, re-install and init providers
// If the user says no, then use the provider create/update operations to update the provider config.
// issue: https://github.com/project-radius/radius/issues/3440
if r.Reinstall {
if r.Reinstall || !r.RadiusInstalled {
// Install radius control plane
err := installRadius(ctx, r)
if err != nil {
Expand All @@ -206,18 +214,18 @@ func (r *Runner) Run(ctx context.Context) error {
if err != nil {
return err
}

//ignore the id of the resource group created
isGroupCreated, err := client.CreateUCPGroup(ctx, "radius", "local", r.EnvName, v20220315privatepreview.ResourceGroupResource{})
if err != nil || !isGroupCreated {
return &cli.FriendlyError{Message: "Failed to create ucp resource group"}
}

isEnvCreated, err := client.CreateEnvironment(ctx, r.EnvName, "global", r.NameSpace, "Kubernetes", "")
isEnvCreated, err := client.CreateEnvironment(ctx, r.EnvName, "global", r.NameSpace, "kubernetes", "")
if err != nil || !isEnvCreated {
return &cli.FriendlyError{Message: "Failed to create radius environment"}
}

err = r.ConfigFileInterface.EditWorkspaces(ctx, r.ConfigHolder.ConfigFilePath, r.Workspace.Name, r.EnvName)
err = r.ConfigFileInterface.EditWorkspaces(ctx, config, r.Workspace, r.AzureCloudProvider)
if err != nil {
return err
}
Expand Down Expand Up @@ -261,7 +269,10 @@ 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 index == 0 {
return currentContext, nil
}
return values[index], nil
}
return currentContext, nil
Expand Down
53 changes: 27 additions & 26 deletions pkg/cli/cmd/radInit/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (

"github.com/golang/mock/gomock"
"github.com/project-radius/radius/pkg/cli/clients"
"github.com/project-radius/radius/pkg/cli/configFile"
"github.com/project-radius/radius/pkg/cli/connections"
"github.com/project-radius/radius/pkg/cli/framework"
"github.com/project-radius/radius/pkg/cli/helm"
Expand All @@ -32,21 +31,21 @@ func Test_CommandValidation(t *testing.T) {
func Test_Validate(t *testing.T) {
ctrl := gomock.NewController(t)
configWithWorkspace := radcli.LoadConfigWithWorkspace(t)
configWithoutWorkspace := radcli.LoadConfigWithoutWorkspace(t)

// Scenario with no cloud provider
kubernetesMock := kubernetes.NewMockInterface(ctrl)
prompter := prompt.NewMockInterface(ctrl)
helmMock := helm.NewMockInterface(ctrl)

initMocksWithoutCloudProvider(kubernetesMock, prompter, helmMock)
// Scenario with error kubeContext read
initMocksWithKubeContextReadError(kubernetesMock)
// Scenario with error kubeContext selection
initMocksWithKubeContextSelectionError(kubernetesMock, prompter)
// Scenario with error env name read
initMocksWithErrorEnvNameRead(kubernetesMock, prompter)
initMocksWithErrorEnvNameRead(kubernetesMock, prompter, helmMock)
// Scenario with error name space read
initMocksWithErrorNamespaceRead(kubernetesMock, prompter)
initMocksWithErrorNamespaceRead(kubernetesMock, prompter, helmMock)
testcases := []radcli.ValidateInput{
{
Name: "Valid Init Command",
Expand All @@ -60,15 +59,6 @@ func Test_Validate(t *testing.T) {
Prompter: prompter,
HelmInterface: helmMock,
},
{
Name: "Init Command with no workspace",
Input: []string{},
ExpectedValid: false,
ConfigHolder: framework.ConfigHolder{
ConfigFilePath: "",
Config: configWithoutWorkspace,
},
},
{
Name: "Init Command With Error KubeContext Read",
Input: []string{},
Expand Down Expand Up @@ -100,6 +90,7 @@ func Test_Validate(t *testing.T) {
},
KubernetesInterface: kubernetesMock,
Prompter: prompter,
HelmInterface: helmMock,
},
{
Name: "Init Command With Error Namespace Read",
Expand All @@ -111,6 +102,7 @@ func Test_Validate(t *testing.T) {
},
KubernetesInterface: kubernetesMock,
Prompter: prompter,
HelmInterface: helmMock,
},
//TODO: Add scenario for init with cloud provider when cloud provider operation is implemented
}
Expand All @@ -120,18 +112,22 @@ func Test_Validate(t *testing.T) {
func Test_Run(t *testing.T) {
t.Run("Init Radius", func(t *testing.T) {
ctrl := gomock.NewController(t)
configFileInterface := framework.NewMockConfigFileInterface(ctrl)
configFileInterface.EXPECT().
ConfigFromContext(context.Background()).
Return(nil).Times(1)

appManagementClient := clients.NewMockApplicationsManagementClient(ctrl)
appManagementClient.EXPECT().
CreateUCPGroup(context.Background(), "radius", "local", "default", gomock.Any()).
Return(true, nil).Times(1)
appManagementClient.EXPECT().
CreateEnvironment(context.Background(), "default", "global", "defaultNameSpace", "Kubernetes", gomock.Any()).
CreateEnvironment(context.Background(), "default", "global", "defaultNameSpace", "kubernetes", gomock.Any()).
Return(true, nil).Times(1)

configFileInterface := configFile.NewMockInterface(ctrl)

configFileInterface.EXPECT().
EditWorkspaces(context.Background(), "filePath", "defaultWorkspace", "default").
EditWorkspaces(context.Background(), gomock.Any(), gomock.Any(), gomock.Any()).
Return(nil).Times(1)

outputSink := &output.MockOutput{}
Expand All @@ -151,6 +147,7 @@ func Test_Run(t *testing.T) {
KubeContext: "kind-kind",
EnvName: "default",
NameSpace: "defaultNameSpace",
Reinstall: true,
}

err := runner.Run(context.Background())
Expand All @@ -161,10 +158,11 @@ func Test_Run(t *testing.T) {
func initMocksWithoutCloudProvider(kubernetesMock *kubernetes.MockInterface, prompterMock *prompt.MockInterface, helmMock *helm.MockInterface) {
initGetKubeContextSuccess(kubernetesMock)
initKubeContextWithKind(prompterMock)
initHelmMockRadiusInstalled(helmMock)
initRadiusReInstallNo(prompterMock)
initEnvNamePrompt(prompterMock)
initNameSpacePrompt(prompterMock)
initAddCloudProviderPromptNo(prompterMock)
initHelmMockRadiusInstalled(helmMock)
}

func initMocksWithKubeContextReadError(kubernetesMock *kubernetes.MockInterface) {
Expand All @@ -173,19 +171,22 @@ func initMocksWithKubeContextReadError(kubernetesMock *kubernetes.MockInterface)

func initMocksWithKubeContextSelectionError(kubernetesMock *kubernetes.MockInterface, prompterMock *prompt.MockInterface) {
initGetKubeContextSuccess(kubernetesMock)
initDefaultKubeContextPromptNo(prompterMock)
initKubeContextSelectionError(prompterMock)
}

func initMocksWithErrorEnvNameRead(kubernetesMock *kubernetes.MockInterface, prompterMock *prompt.MockInterface) {
func initMocksWithErrorEnvNameRead(kubernetesMock *kubernetes.MockInterface, prompterMock *prompt.MockInterface, helmMock *helm.MockInterface) {
initGetKubeContextSuccess(kubernetesMock)
initKubeContextWithKind(prompterMock)
initHelmMockRadiusInstalled(helmMock)
initRadiusReInstallNo(prompterMock)
initEnvNamePromptError(prompterMock)
}

func initMocksWithErrorNamespaceRead(kubernetesMock *kubernetes.MockInterface, prompterMock *prompt.MockInterface) {
func initMocksWithErrorNamespaceRead(kubernetesMock *kubernetes.MockInterface, prompterMock *prompt.MockInterface, helmMock *helm.MockInterface) {
initGetKubeContextSuccess(kubernetesMock)
initKubeContextWithKind(prompterMock)
initHelmMockRadiusInstalled(helmMock)
initRadiusReInstallNo(prompterMock)
initEnvNamePrompt(prompterMock)
initNameSpacePromptError(prompterMock)
}
Expand Down Expand Up @@ -214,12 +215,6 @@ func getTestKubeConfig() *api.Config {
}
}

func initDefaultKubeContextPromptNo(prompter *prompt.MockInterface) {
prompter.EXPECT().
RunPrompt(gomock.Any()).
Return("N", nil).Times(1)
}

func initKubeContextWithKind(prompter *prompt.MockInterface) {
prompter.EXPECT().
RunSelect(gomock.Any()).
Expand All @@ -232,6 +227,12 @@ func initKubeContextSelectionError(prompter *prompt.MockInterface) {
Return(-1, "", errors.New("cannot read selection")).Times(1)
}

func initRadiusReInstallNo(prompter *prompt.MockInterface) {
prompter.EXPECT().
RunPrompt(gomock.Any()).
Return("N", nil).Times(1)
}

func initEnvNamePrompt(prompter *prompt.MockInterface) {
prompter.EXPECT().
RunPrompt(gomock.Any()).
Expand Down
Loading

0 comments on commit 1abab86

Please sign in to comment.