From d01ecf931c958dfb6913fd8ba923cb599ebc6a16 Mon Sep 17 00:00:00 2001 From: SoTrx <11771975+SoTrx@users.noreply.github.com> Date: Tue, 13 Aug 2024 18:21:59 +0200 Subject: [PATCH] Add warning when deleting an environment with existing applications (#7786) # Description Add additional warning for deleting environments with existing applications. ## Type of change - This pull request adds or changes features of Radius and has an approved issue (issue link required). Fixes: #7529 --------- Signed-off-by: SoTrx <11771975+SoTrx@users.noreply.github.com> Signed-off-by: SoTrxII <11771975+SoTrx@users.noreply.github.com> --- pkg/cli/cmd/env/delete/delete.go | 32 ++++- pkg/cli/cmd/env/delete/delete_test.go | 185 ++++++++++++++++++++++++-- 2 files changed, 203 insertions(+), 14 deletions(-) diff --git a/pkg/cli/cmd/env/delete/delete.go b/pkg/cli/cmd/env/delete/delete.go index 3de3462761..60b8565b74 100644 --- a/pkg/cli/cmd/env/delete/delete.go +++ b/pkg/cli/cmd/env/delete/delete.go @@ -19,6 +19,7 @@ package delete import ( "context" "fmt" + "strings" "github.com/radius-project/radius/pkg/cli" "github.com/radius-project/radius/pkg/cli/cmd/commonflags" @@ -31,6 +32,7 @@ import ( ) const ( + warnDependencies = "There are currently application(s) or resource(s) associated with this environment." deleteConfirmation = "Are you sure you want to delete environment '%v'?" ) @@ -140,9 +142,32 @@ func (r *Runner) Validate(cmd *cobra.Command, args []string) error { // Run prompts the user to confirm the deletion of an environment, creates an applications management client, and // deletes the environment if confirmed. It returns an error if the prompt or client creation fails. func (r *Runner) Run(ctx context.Context) error { + client, err := r.ConnectionFactory.CreateApplicationsManagementClient(ctx, *r.Workspace) + if err != nil { + return err + } + // Prompt user to confirm deletion if !r.Confirm { - confirmed, err := prompt.YesOrNoPrompt(fmt.Sprintf(deleteConfirmation, r.EnvironmentName), prompt.ConfirmNo, r.InputPrompter) + // Doesn't list applications + resourcesInEnvironment, err := client.ListResourcesInEnvironment(ctx, r.EnvironmentName) + if err != nil { + return err + } + + appsInEnvironment, err := client.ListResourcesOfTypeInEnvironment(ctx, r.EnvironmentName, "Applications.Core/applications") + if err != nil { + return err + } + + var promptBuilder strings.Builder + if len(appsInEnvironment) > 0 || len(resourcesInEnvironment) > 0 { + promptBuilder.WriteString(warnDependencies) + promptBuilder.WriteString(" ") + } + promptBuilder.WriteString(fmt.Sprintf(deleteConfirmation, r.EnvironmentName)) + + confirmed, err := prompt.YesOrNoPrompt(promptBuilder.String(), prompt.ConfirmNo, r.InputPrompter) if err != nil { return err } @@ -151,11 +176,6 @@ func (r *Runner) Run(ctx context.Context) error { } } - client, err := r.ConnectionFactory.CreateApplicationsManagementClient(ctx, *r.Workspace) - if err != nil { - return err - } - deleted, err := client.DeleteEnvironment(ctx, r.EnvironmentName) if err != nil { return err diff --git a/pkg/cli/cmd/env/delete/delete_test.go b/pkg/cli/cmd/env/delete/delete_test.go index 2dd4cc6030..b7572f1b84 100644 --- a/pkg/cli/cmd/env/delete/delete_test.go +++ b/pkg/cli/cmd/env/delete/delete_test.go @@ -19,6 +19,8 @@ package delete import ( "context" "fmt" + "github.com/radius-project/radius/pkg/cli/clients_new/generated" + "strings" "testing" "github.com/radius-project/radius/pkg/cli/clients" @@ -150,6 +152,79 @@ func Test_Show(t *testing.T) { Times(1) appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) + + appManagementClient.EXPECT(). + ListResourcesInEnvironment(gomock.Any(), "test-env"). + Return([]generated.GenericResource{}, nil). + Times(1) + + appManagementClient.EXPECT(). + ListResourcesOfTypeInEnvironment(gomock.Any(), "test-env", "Applications.Core/applications"). + Return([]generated.GenericResource{}, nil). + Times(1) + + appManagementClient.EXPECT(). + DeleteEnvironment(gomock.Any(), "test-env"). + Return(true, nil). + Times(1) + + workspace := &workspaces.Workspace{ + Connection: map[string]any{ + "kind": "kubernetes", + "context": "kind-kind", + }, + Name: "kind-kind", + Scope: "/planes/radius/local/resourceGroups/test-group", + } + outputSink := &output.MockOutput{} + runner := &Runner{ + ConnectionFactory: &connections.MockFactory{ApplicationsManagementClient: appManagementClient}, + InputPrompter: promptMock, + Workspace: workspace, + Format: "table", + Output: outputSink, + EnvironmentName: "test-env", + } + + err := runner.Run(context.Background()) + require.NoError(t, err) + + expected := []any{ + output.LogOutput{ + Format: "Environment deleted", + }, + } + + require.Equal(t, expected, outputSink.Writes) + }) + t.Run("Success: Prompt Confirmed, existing applications", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + expectedPrompt := strings.Join([]string{ + warnDependencies, + fmt.Sprintf(deleteConfirmation, "test-env"), + }, " ") + promptMock := prompt.NewMockInterface(ctrl) + promptMock.EXPECT(). + GetListInput([]string{prompt.ConfirmNo, prompt.ConfirmYes}, expectedPrompt). + Return(prompt.ConfirmYes, nil). + Times(1) + + appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) + + appManagementClient.EXPECT(). + ListResourcesInEnvironment(gomock.Any(), "test-env"). + Return([]generated.GenericResource{}, nil). + Times(1) + + appManagementClient.EXPECT(). + ListResourcesOfTypeInEnvironment(gomock.Any(), "test-env", "Applications.Core/applications"). + Return([]generated.GenericResource{ + {}, + }, nil). + Times(1) + appManagementClient.EXPECT(). DeleteEnvironment(gomock.Any(), "test-env"). Return(true, nil). @@ -184,7 +259,68 @@ func Test_Show(t *testing.T) { require.Equal(t, expected, outputSink.Writes) }) + t.Run("Success: Prompt Confirmed, existing resources", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + expectedPrompt := strings.Join([]string{ + warnDependencies, + fmt.Sprintf(deleteConfirmation, "test-env"), + }, " ") + promptMock := prompt.NewMockInterface(ctrl) + promptMock.EXPECT(). + GetListInput([]string{prompt.ConfirmNo, prompt.ConfirmYes}, expectedPrompt). + Return(prompt.ConfirmYes, nil). + Times(1) + + appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) + + appManagementClient.EXPECT(). + ListResourcesInEnvironment(gomock.Any(), "test-env"). + Return([]generated.GenericResource{ + {}, + }, nil). + Times(1) + + appManagementClient.EXPECT(). + ListResourcesOfTypeInEnvironment(gomock.Any(), "test-env", "Applications.Core/applications"). + Return([]generated.GenericResource{}, nil). + Times(1) + + appManagementClient.EXPECT(). + DeleteEnvironment(gomock.Any(), "test-env"). + Return(true, nil). + Times(1) + + workspace := &workspaces.Workspace{ + Connection: map[string]any{ + "kind": "kubernetes", + "context": "kind-kind", + }, + Name: "kind-kind", + Scope: "/planes/radius/local/resourceGroups/test-group", + } + outputSink := &output.MockOutput{} + runner := &Runner{ + ConnectionFactory: &connections.MockFactory{ApplicationsManagementClient: appManagementClient}, + InputPrompter: promptMock, + Workspace: workspace, + Format: "table", + Output: outputSink, + EnvironmentName: "test-env", + } + + err := runner.Run(context.Background()) + require.NoError(t, err) + + expected := []any{ + output.LogOutput{ + Format: "Environment deleted", + }, + } + + require.Equal(t, expected, outputSink.Writes) + }) t.Run("Success: Prompt Cancelled", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -195,6 +331,18 @@ func Test_Show(t *testing.T) { Return(prompt.ConfirmNo, nil). Times(1) + appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) + + appManagementClient.EXPECT(). + ListResourcesInEnvironment(gomock.Any(), "test-env"). + Return([]generated.GenericResource{}, nil). + Times(1) + + appManagementClient.EXPECT(). + ListResourcesOfTypeInEnvironment(gomock.Any(), "test-env", "Applications.Core/applications"). + Return([]generated.GenericResource{}, nil). + Times(1) + workspace := &workspaces.Workspace{ Connection: map[string]any{ "kind": "kubernetes", @@ -205,11 +353,12 @@ func Test_Show(t *testing.T) { } outputSink := &output.MockOutput{} runner := &Runner{ - InputPrompter: promptMock, - Workspace: workspace, - Format: "table", - Output: outputSink, - EnvironmentName: "test-env", + ConnectionFactory: &connections.MockFactory{ApplicationsManagementClient: appManagementClient}, + InputPrompter: promptMock, + Workspace: workspace, + Format: "table", + Output: outputSink, + EnvironmentName: "test-env", } err := runner.Run(context.Background()) @@ -273,11 +422,31 @@ func Test_Show(t *testing.T) { Return("", &prompt.ErrExitConsole{}). Times(1) + appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) + appManagementClient.EXPECT(). + ListResourcesInEnvironment(gomock.Any(), "test-env"). + Return([]generated.GenericResource{}, nil). + Times(1) + appManagementClient.EXPECT(). + ListResourcesOfTypeInEnvironment(gomock.Any(), "test-env", "Applications.Core/applications"). + Return([]generated.GenericResource{}, nil). + Times(1) + outputSink := &output.MockOutput{} + workspace := &workspaces.Workspace{ + Connection: map[string]any{ + "kind": "kubernetes", + "context": "kind-kind", + }, + Name: "kind-kind", + Scope: "/planes/radius/local/resourceGroups/test-group", + } runner := &Runner{ - InputPrompter: promptMock, - Output: outputSink, - EnvironmentName: "test-env", + ConnectionFactory: &connections.MockFactory{ApplicationsManagementClient: appManagementClient}, + Workspace: workspace, + InputPrompter: promptMock, + Output: outputSink, + EnvironmentName: "test-env", } err := runner.Run(context.Background())