diff --git a/pkg/cli/cmd/app/delete/delete.go b/pkg/cli/cmd/app/delete/delete.go index 6a90ebd29f..b09489c124 100644 --- a/pkg/cli/cmd/app/delete/delete.go +++ b/pkg/cli/cmd/app/delete/delete.go @@ -173,7 +173,7 @@ func (r *Runner) Run(ctx context.Context) error { app, err := client.GetApplication(ctx, r.ApplicationName) if clients.Is404Error(err) { - r.Output.LogInfo("Application '%s' does not exist or has already been deleted.", r.ApplicationName) + r.Output.LogInfo("Applications.Core/applications/%s not found", r.ApplicationName) return nil } else if err != nil { return err @@ -210,15 +210,15 @@ func (r *Runner) Run(ctx context.Context) error { }) if err != nil { if strings.Contains(err.Error(), "not found") { - r.Output.LogInfo("Application '%s' does not exist or has already been deleted.", r.ApplicationName) + r.Output.LogInfo("Applications.Core/applications/%s not found", r.ApplicationName) return nil } return clierrors.Message("Failed to delete application '%s': %v", r.ApplicationName, err) } if deleted { - r.Output.LogInfo("Application %s deleted successfully", r.ApplicationName) + r.Output.LogInfo("Applications.Core/applications/%s deleted", r.ApplicationName) } else { - r.Output.LogInfo("Application '%s' does not exist or has already been deleted.", r.ApplicationName) + r.Output.LogInfo("Applications.Core/applications/%s not found", r.ApplicationName) return nil } diff --git a/pkg/cli/cmd/app/delete/delete_test.go b/pkg/cli/cmd/app/delete/delete_test.go index f7c2d765b8..9b80da15e0 100644 --- a/pkg/cli/cmd/app/delete/delete_test.go +++ b/pkg/cli/cmd/app/delete/delete_test.go @@ -171,7 +171,7 @@ func Test_Delete(t *testing.T) { expected := []any{ output.LogOutput{ - Format: "Application %s deleted successfully", + Format: "Applications.Core/applications/%s deleted", Params: []any{"test-app"}, }, } @@ -240,7 +240,7 @@ func Test_Delete(t *testing.T) { expected := []any{ output.LogOutput{ - Format: "Application %s deleted successfully", + Format: "Applications.Core/applications/%s deleted", Params: []any{"test-app"}, }, } @@ -354,7 +354,7 @@ func Test_Delete(t *testing.T) { expected := []any{ output.LogOutput{ - Format: "Application '%s' does not exist or has already been deleted.", + Format: "Applications.Core/applications/%s not found", Params: []any{"test-app"}, }, } @@ -522,7 +522,69 @@ func Test_Delete(t *testing.T) { expected := []any{ output.LogOutput{ - Format: "Application '%s' does not exist or has already been deleted.", + Format: "Applications.Core/applications/%s not found", + Params: []any{"test-app"}, + }, + } + + require.Equal(t, expected, outputSink.Writes) + }) + + t.Run("Success: Delete Returns False (already gone)", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) + deleteMock := delete.NewMockInterface(ctrl) + + appManagementClient.EXPECT(). + GetApplication(gomock.Any(), "test-app"). + Return(v20231001preview.ApplicationResource{ + Properties: &v20231001preview.ApplicationProperties{ + Environment: new("/planes/radius/local/resourceGroups/default/providers/Applications.Core/environments/default"), + }, + }, nil). + Times(1) + + progressText := fmt.Sprintf("Deleting application '%s' from environment '%s'...", "test-app", "default") + deleteMock.EXPECT(). + DeleteApplicationWithProgress( + gomock.Any(), + appManagementClient, + clients.DeleteOptions{ + ApplicationNameOrID: "test-app", + ProgressText: progressText, + }, + ). + Return(false, 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", + Environment: "/planes/radius/local/resourceGroups/default/providers/Applications.Core/environments/default", + } + outputSink := &output.MockOutput{} + runner := &Runner{ + Delete: deleteMock, + ConnectionFactory: &connections.MockFactory{ApplicationsManagementClient: appManagementClient}, + Workspace: workspace, + Output: outputSink, + ApplicationName: "test-app", + EnvironmentName: "default", + Confirm: true, + } + + err := runner.Run(context.Background()) + require.NoError(t, err) + + expected := []any{ + output.LogOutput{ + Format: "Applications.Core/applications/%s not found", Params: []any{"test-app"}, }, } @@ -591,6 +653,6 @@ func Test_Delete(t *testing.T) { require.Contains(t, warningOutput.Format, "WARNING") lastOutput, ok := outputSink.Writes[len(outputSink.Writes)-1].(output.LogOutput) require.True(t, ok) - require.Equal(t, "Application %s deleted successfully", lastOutput.Format) + require.Equal(t, "Applications.Core/applications/%s deleted", lastOutput.Format) }) } diff --git a/pkg/cli/cmd/env/create/create.go b/pkg/cli/cmd/env/create/create.go index de04f3acb0..548d0ad1a3 100644 --- a/pkg/cli/cmd/env/create/create.go +++ b/pkg/cli/cmd/env/create/create.go @@ -150,8 +150,6 @@ func (r *Runner) Validate(cmd *cobra.Command, args []string) error { // Run creates an environment in the specified resource group using the provided environment name and namespace, and // returns an error if unsuccessful. func (r *Runner) Run(ctx context.Context) error { - r.Output.LogInfo("Creating Environment...") - client, err := r.ConnectionFactory.CreateApplicationsManagementClient(ctx, *r.Workspace) if err != nil { return err @@ -170,7 +168,7 @@ func (r *Runner) Run(ctx context.Context) error { if err != nil { return err } - r.Output.LogInfo("Successfully created environment %q in resource group %q", r.EnvironmentName, r.ResourceGroupName) + r.Output.LogInfo("Applications.Core/environments/%s created", r.EnvironmentName) return nil } diff --git a/pkg/cli/cmd/env/create/create_test.go b/pkg/cli/cmd/env/create/create_test.go index ed5df50d23..2450ff707a 100644 --- a/pkg/cli/cmd/env/create/create_test.go +++ b/pkg/cli/cmd/env/create/create_test.go @@ -188,13 +188,9 @@ func Test_Run(t *testing.T) { expectedOutput := []any{ output.LogOutput{ - Format: "Creating Environment...", - }, - output.LogOutput{ - Format: "Successfully created environment %q in resource group %q", + Format: "Applications.Core/environments/%s created", Params: []any{ "default", - "test-group", }, }, } @@ -247,16 +243,10 @@ func Test_Run(t *testing.T) { ConfigFileInterface: configFileInterface, } - expectedOutput := []any{ - output.LogOutput{ - Format: "Creating Environment...", - }, - } - err := runner.Run(context.Background()) require.Error(t, err) require.Equal(t, expectedError, err) - require.Equal(t, expectedOutput, outputSink.Writes) + require.Empty(t, outputSink.Writes) }) } diff --git a/pkg/cli/cmd/env/create/preview/create.go b/pkg/cli/cmd/env/create/preview/create.go index 52e090c0f1..7f490a4199 100644 --- a/pkg/cli/cmd/env/create/preview/create.go +++ b/pkg/cli/cmd/env/create/preview/create.go @@ -145,8 +145,6 @@ func (r *Runner) Run(ctx context.Context) error { r.RadiusCoreClientFactory = clientFactory } - r.Output.LogInfo("Creating Radius Core Environment %q...", r.EnvironmentName) - // Ensure the default resource group exists before creating recipe pack in it. mgmtClient, err := r.ConnectionFactory.CreateApplicationsManagementClient(ctx, *r.Workspace) if err != nil { @@ -185,6 +183,6 @@ func (r *Runner) Run(ctx context.Context) error { return err } - r.Output.LogInfo("Successfully created environment %q in resource group %q with default recipe pack.", r.EnvironmentName, r.ResourceGroupName) - return nil + r.Output.LogInfo("Radius.Core/environments/%s created", r.EnvironmentName) + return nil } diff --git a/pkg/cli/cmd/env/create/preview/create_test.go b/pkg/cli/cmd/env/create/preview/create_test.go index f874640f5b..a9bbfcda94 100644 --- a/pkg/cli/cmd/env/create/preview/create_test.go +++ b/pkg/cli/cmd/env/create/preview/create_test.go @@ -189,17 +189,18 @@ func Test_Run(t *testing.T) { ResourceGroupName: "test-resource-group", } + expectedOutput := []any{ + output.LogOutput{ + Format: "Radius.Core/environments/%s created", + Params: []any{ + "testenv", + }, + }, + } + err = runner.Run(context.Background()) require.NoError(t, err) - - require.Contains(t, outputSink.Writes, output.LogOutput{ - Format: "Creating Radius Core Environment %q...", - Params: []interface{}{"testenv"}, - }) - require.Contains(t, outputSink.Writes, output.LogOutput{ - Format: "Successfully created environment %q in resource group %q with default recipe pack.", - Params: []interface{}{"testenv", "test-resource-group"}, - }) + require.Equal(t, expectedOutput, outputSink.Writes) }) t.Run("creates default recipe pack when not found", func(t *testing.T) { diff --git a/pkg/cli/cmd/env/delete/delete.go b/pkg/cli/cmd/env/delete/delete.go index 520937643a..b9b1d98d42 100644 --- a/pkg/cli/cmd/env/delete/delete.go +++ b/pkg/cli/cmd/env/delete/delete.go @@ -31,10 +31,8 @@ import ( ) const ( - msgEnvironmentDeleted = "Environment deleted" - msgEnvironmentNotFound = "Environment '%s' does not exist or has already been deleted." - msgDeletingEnvironment = "Deleting environment %s...\n" - msgDeletingResourceCount = "Deleting %d resource(s) in environment %s...\n" + msgEnvironmentDeleted = "Applications.Core/environments/%s deleted" + msgEnvironmentNotFound = "Applications.Core/environments/%s not found" ) // NewCommand creates an instance of the command and runner for the `rad env delete` command. @@ -166,24 +164,17 @@ func (r *Runner) Run(ctx context.Context) error { return err } if !confirmed { - r.Output.LogInfo("Environment %q NOT deleted", r.EnvironmentName) return nil } } - // Show progress messages - if totalResourceCount > 0 { - r.Output.LogInfo(msgDeletingResourceCount, totalResourceCount, r.EnvironmentName) - } - r.Output.LogInfo(msgDeletingEnvironment, r.EnvironmentName) - deleted, err := client.DeleteEnvironment(ctx, r.EnvironmentName) if err != nil { return err } if deleted { - r.Output.LogInfo(msgEnvironmentDeleted) + r.Output.LogInfo(msgEnvironmentDeleted, r.EnvironmentName) } else { r.Output.LogInfo(msgEnvironmentNotFound, r.EnvironmentName) } diff --git a/pkg/cli/cmd/env/delete/delete_test.go b/pkg/cli/cmd/env/delete/delete_test.go index 0c528d9b16..9c3aebf5b5 100644 --- a/pkg/cli/cmd/env/delete/delete_test.go +++ b/pkg/cli/cmd/env/delete/delete_test.go @@ -39,7 +39,7 @@ func Test_CommandValidation(t *testing.T) { } const ( - deleteConfirmationEmpty = "The environment %s is empty. Are you sure you want to delete the environment?" + deleteConfirmationEmpty = "The environment %s is empty. Are you sure you want to delete the environment?" deleteConfirmationWithResources = "The environment %s contains %d deployed resource(s). Are you sure you want to delete the environment and its resources?" ) @@ -145,12 +145,9 @@ func Test_Show(t *testing.T) { require.NoError(t, err) expected := []any{ - output.LogOutput{ - Format: msgDeletingEnvironment, - Params: []any{"test-env"}, - }, output.LogOutput{ Format: msgEnvironmentDeleted, + Params: []any{"test-env"}, }, } @@ -201,12 +198,9 @@ func Test_Show(t *testing.T) { require.NoError(t, err) expected := []any{ - output.LogOutput{ - Format: msgDeletingEnvironment, - Params: []any{"test-env"}, - }, output.LogOutput{ Format: msgEnvironmentDeleted, + Params: []any{"test-env"}, }, } @@ -260,16 +254,9 @@ func Test_Show(t *testing.T) { require.NoError(t, err) expected := []any{ - output.LogOutput{ - Format: msgDeletingResourceCount, - Params: []any{1, "test-env"}, - }, - output.LogOutput{ - Format: msgDeletingEnvironment, - Params: []any{"test-env"}, - }, output.LogOutput{ Format: msgEnvironmentDeleted, + Params: []any{"test-env"}, }, } @@ -323,16 +310,9 @@ func Test_Show(t *testing.T) { require.NoError(t, err) expected := []any{ - output.LogOutput{ - Format: msgDeletingResourceCount, - Params: []any{1, "test-env"}, - }, - output.LogOutput{ - Format: msgDeletingEnvironment, - Params: []any{"test-env"}, - }, output.LogOutput{ Format: msgEnvironmentDeleted, + Params: []any{"test-env"}, }, } @@ -376,13 +356,7 @@ func Test_Show(t *testing.T) { err := runner.Run(context.Background()) require.NoError(t, err) - expected := []any{ - output.LogOutput{ - Format: "Environment %q NOT deleted", - Params: []any{"test-env"}, - }, - } - require.Equal(t, expected, outputSink.Writes) + require.Empty(t, outputSink.Writes) }) // YES, this is a success case. Delete means "make it be gone", so if the environment is already @@ -428,10 +402,6 @@ func Test_Show(t *testing.T) { require.NoError(t, err) expected := []any{ - output.LogOutput{ - Format: msgDeletingEnvironment, - Params: []any{"test-env"}, - }, output.LogOutput{ Format: msgEnvironmentNotFound, Params: []any{"test-env"}, diff --git a/pkg/cli/cmd/env/delete/preview/delete.go b/pkg/cli/cmd/env/delete/preview/delete.go index 3d27dbf01c..8733a6fb0a 100644 --- a/pkg/cli/cmd/env/delete/preview/delete.go +++ b/pkg/cli/cmd/env/delete/preview/delete.go @@ -33,10 +33,8 @@ import ( ) const ( - msgEnvironmentDeletedPreview = "Environment deleted" - msgEnvironmentNotFoundPreview = "Environment '%s' does not exist or has already been deleted." - msgDeletingEnvironmentPreview = "Deleting environment %s...\n" - msgDeletingResourceCountPreview = "Deleting %d resource(s) in environment %s...\n" + msgEnvironmentDeletedPreview = "Radius.Core/environments/%s deleted" + msgEnvironmentNotFoundPreview = "Radius.Core/environments/%s not found" ) // NewCommand creates an instance of the command and runner for the `rad env delete --preview` command. @@ -138,14 +136,10 @@ func (r *Runner) Run(ctx context.Context) error { return err } if !confirmed { - r.Output.LogInfo("Environment %q NOT deleted", r.EnvironmentName) return nil } } - // Show progress messages (without resource count for preview, since we don't enumerate here) - r.Output.LogInfo(msgDeletingEnvironmentPreview, r.EnvironmentName) - client := r.RadiusCoreClientFactory.NewEnvironmentsClient() _, err := client.Delete(ctx, r.EnvironmentName, &corerpv20250801.EnvironmentsClientDeleteOptions{}) if err != nil { @@ -154,7 +148,7 @@ func (r *Runner) Run(ctx context.Context) error { return err } - r.Output.LogInfo(msgEnvironmentDeletedPreview) + r.Output.LogInfo(msgEnvironmentDeletedPreview, r.EnvironmentName) return nil } diff --git a/pkg/cli/cmd/env/delete/preview/delete_test.go b/pkg/cli/cmd/env/delete/preview/delete_test.go index 320cb5a9ea..463075273a 100644 --- a/pkg/cli/cmd/env/delete/preview/delete_test.go +++ b/pkg/cli/cmd/env/delete/preview/delete_test.go @@ -98,12 +98,9 @@ func Test_Run(t *testing.T) { name: "Success: environment deleted", serverFactory: test_client_factory.WithEnvironmentServerNoError, expectedLogs: []any{ - output.LogOutput{ - Format: msgDeletingEnvironmentPreview, - Params: []any{"test-env"}, - }, output.LogOutput{ Format: msgEnvironmentDeletedPreview, + Params: []any{"test-env"}, }, }, }, diff --git a/pkg/cli/cmd/env/update/objectformats.go b/pkg/cli/cmd/env/update/objectformats.go deleted file mode 100644 index 5db2208194..0000000000 --- a/pkg/cli/cmd/env/update/objectformats.go +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright 2023 The Radius Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package update - -import "github.com/radius-project/radius/pkg/cli/output" - -type environmentForDisplay struct { - Name string - ComputeKind string - Recipes int - Providers int -} - -// environmentFormat returns a FormatterOptions object containing the column headings and JSONPaths for the -// environment table. -func environmentFormat() output.FormatterOptions { - return output.FormatterOptions{ - Columns: []output.Column{ - { - Heading: "NAME", - JSONPath: "{ .Name }", - }, - { - Heading: "COMPUTE", - JSONPath: "{ .ComputeKind }", - }, - { - Heading: "RECIPES", - JSONPath: "{ .Recipes }", - }, - { - Heading: "PROVIDERS", - JSONPath: "{ .Providers }", - }, - }, - } -} diff --git a/pkg/cli/cmd/env/update/objectformats_test.go b/pkg/cli/cmd/env/update/objectformats_test.go deleted file mode 100644 index b8ffecc228..0000000000 --- a/pkg/cli/cmd/env/update/objectformats_test.go +++ /dev/null @@ -1,41 +0,0 @@ -/* -Copyright 2023 The Radius Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package update - -import ( - "bytes" - "testing" - - "github.com/radius-project/radius/pkg/cli/output" - "github.com/stretchr/testify/require" -) - -func Test_environmentFormat(t *testing.T) { - obj := environmentForDisplay{ - Name: "test_env_resource", - ComputeKind: "kubernetes", - Recipes: 3, - Providers: 2, - } - - buffer := &bytes.Buffer{} - err := output.Write(output.FormatTable, obj, buffer, environmentFormat()) - require.NoError(t, err) - - expected := "NAME COMPUTE RECIPES PROVIDERS\ntest_env_resource kubernetes 3 2\n" - require.Equal(t, expected, buffer.String()) -} diff --git a/pkg/cli/cmd/env/update/preview/objectformats.go b/pkg/cli/cmd/env/update/preview/objectformats.go deleted file mode 100644 index 4b250d7c69..0000000000 --- a/pkg/cli/cmd/env/update/preview/objectformats.go +++ /dev/null @@ -1,46 +0,0 @@ -/* -Copyright 2023 The Radius Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package preview - -import "github.com/radius-project/radius/pkg/cli/output" - -type environmentForDisplay struct { - Name string - RecipePacks int - Providers int -} - -// environmentFormat returns a FormatterOptions object containing the column headings and JSONPaths for the -// environment table. -func environmentFormat() output.FormatterOptions { - return output.FormatterOptions{ - Columns: []output.Column{ - { - Heading: "NAME", - JSONPath: "{ .Name }", - }, - { - Heading: "RECIPE PACKS", - JSONPath: "{ .RecipePacks }", - }, - { - Heading: "PROVIDERS", - JSONPath: "{ .Providers }", - }, - }, - } -} diff --git a/pkg/cli/cmd/env/update/preview/update.go b/pkg/cli/cmd/env/update/preview/update.go index 4ce8221e2a..80b2e3d39e 100644 --- a/pkg/cli/cmd/env/update/preview/update.go +++ b/pkg/cli/cmd/env/update/preview/update.go @@ -356,40 +356,12 @@ func (r *Runner) Run(ctx context.Context) error { env.Properties.RecipePacks = newRecipePacks } - r.Output.LogInfo("Updating Environment...") _, err = envClient.CreateOrUpdate(ctx, r.EnvironmentName, env, &corerpv20250801.EnvironmentsClientCreateOrUpdateOptions{}) if err != nil { return clierrors.MessageWithCause(err, "Failed to update environment %q.", r.EnvironmentName) } - recipePackCount := 0 - if env.Properties.RecipePacks != nil { - recipePackCount = len(env.Properties.RecipePacks) - } - providerCount := 0 - if env.Properties.Providers != nil { - if env.Properties.Providers.Azure != nil { - providerCount++ - } - if env.Properties.Providers.Aws != nil { - providerCount++ - } - if env.Properties.Providers.Kubernetes != nil { - providerCount++ - } - } - obj := environmentForDisplay{ - Name: *env.Name, - RecipePacks: recipePackCount, - Providers: providerCount, - } - - err = r.Output.WriteFormatted("table", obj, environmentFormat()) - if err != nil { - return err - } - - r.Output.LogInfo("Successfully updated environment %q.", r.EnvironmentName) + r.Output.LogInfo("Radius.Core/environments/%s updated", r.EnvironmentName) return nil } diff --git a/pkg/cli/cmd/env/update/preview/update_test.go b/pkg/cli/cmd/env/update/preview/update_test.go index 5011a3153e..437ce1013a 100644 --- a/pkg/cli/cmd/env/update/preview/update_test.go +++ b/pkg/cli/cmd/env/update/preview/update_test.go @@ -306,19 +306,7 @@ func Test_Run(t *testing.T) { Format: "WARNING: The existing recipe pack list will be replaced with the specified packs.", }, output.LogOutput{ - Format: "Updating Environment...", - }, - output.FormattedOutput{ - Format: "table", - Obj: environmentForDisplay{ - Name: "test-env", - RecipePacks: 2, // rp1 and rp2 replace the old pack - Providers: 3, - }, - Options: environmentFormat(), - }, - output.LogOutput{ - Format: "Successfully updated environment %q.", + Format: "Radius.Core/environments/%s updated", Params: []any{"test-env"}, }, }, diff --git a/pkg/cli/cmd/env/update/update.go b/pkg/cli/cmd/env/update/update.go index 3187bee205..86259c79fb 100644 --- a/pkg/cli/cmd/env/update/update.go +++ b/pkg/cli/cmd/env/update/update.go @@ -228,8 +228,6 @@ func (r *Runner) Run(ctx context.Context) error { env.Properties.Providers.Aws = r.providers.Aws } - r.Output.LogInfo("Updating Environment...") - err = client.CreateOrUpdateEnvironment(ctx, r.EnvName, &corerp.EnvironmentResource{ Location: to.Ptr(v1.LocationGlobal), Properties: env.Properties, @@ -238,36 +236,7 @@ func (r *Runner) Run(ctx context.Context) error { return clierrors.MessageWithCause(err, "Failed to apply cloud provider scope to the environment %q.", r.EnvName) } - recipeCount := 0 - if env.Properties.Recipes != nil { - recipeCount = len(env.Properties.Recipes) - } - providerCount := 0 - if env.Properties.Providers != nil { - if env.Properties.Providers.Azure != nil { - providerCount++ - } - if env.Properties.Providers.Aws != nil { - providerCount++ - } - } - computeKind := "" - if env.Properties.Compute != nil { - computeKind = *env.Properties.Compute.GetEnvironmentCompute().Kind - } - obj := environmentForDisplay{ - Name: *env.Name, - ComputeKind: computeKind, - Recipes: recipeCount, - Providers: providerCount, - } - - err = r.Output.WriteFormatted("table", obj, environmentFormat()) - if err != nil { - return err - } - - r.Output.LogInfo("Successfully updated environment %q.", r.EnvName) + r.Output.LogInfo("Applications.Core/environments/%s updated", r.EnvName) return nil } diff --git a/pkg/cli/cmd/env/update/update_test.go b/pkg/cli/cmd/env/update/update_test.go index 44f7b7fb40..7f6a5a97fe 100644 --- a/pkg/cli/cmd/env/update/update_test.go +++ b/pkg/cli/cmd/env/update/update_test.go @@ -341,24 +341,11 @@ func Test_Update(t *testing.T) { require.NoError(t, err) environment.Properties.Providers = testProviders - obj := environmentForDisplay{ - Name: "test-env", - Recipes: 0, - Providers: 2, - ComputeKind: "kubernetes", - } + _ = environment expected := []any{ output.LogOutput{ - Format: "Updating Environment...", - }, - output.FormattedOutput{ - Format: "table", - Obj: obj, - Options: environmentFormat(), - }, - output.LogOutput{ - Format: "Successfully updated environment %q.", + Format: "Applications.Core/environments/%s updated", Params: []any{"test-env"}, }, } @@ -510,34 +497,9 @@ func Test_Update(t *testing.T) { err := runner.Run(context.Background()) require.NoError(t, err) - numberOfProviders := func() int { - numberOfProviders := 0 - if tc.expectedProviders.Azure != nil { - numberOfProviders++ - } - if tc.expectedProviders.Aws != nil { - numberOfProviders++ - } - return numberOfProviders - } - - obj := environmentForDisplay{ - Name: "test-env", - Recipes: 0, - Providers: numberOfProviders(), - } - expected := []any{ output.LogOutput{ - Format: "Updating Environment...", - }, - output.FormattedOutput{ - Format: "table", - Obj: obj, - Options: environmentFormat(), - }, - output.LogOutput{ - Format: "Successfully updated environment %q.", + Format: "Applications.Core/environments/%s updated", Params: []any{"test-env"}, }, } diff --git a/pkg/cli/cmd/group/create/create.go b/pkg/cli/cmd/group/create/create.go index cdcca08f1d..35f3218a4b 100644 --- a/pkg/cli/cmd/group/create/create.go +++ b/pkg/cli/cmd/group/create/create.go @@ -119,8 +119,6 @@ func (r *Runner) Run(ctx context.Context) error { return err } - r.Output.LogInfo("creating resource group %q in workspace %q...\n", r.UCPResourceGroupName, r.Workspace.Name) - err = client.CreateOrUpdateResourceGroup(ctx, "local", r.UCPResourceGroupName, &v20231001preview.ResourceGroupResource{ Location: to.Ptr(v1.LocationGlobal), }) @@ -128,7 +126,7 @@ func (r *Runner) Run(ctx context.Context) error { return err } - r.Output.LogInfo("resource group %q created", r.UCPResourceGroupName) + r.Output.LogInfo("System.Resources/resourceGroups/%s created", r.UCPResourceGroupName) return nil } diff --git a/pkg/cli/cmd/group/create/create_test.go b/pkg/cli/cmd/group/create/create_test.go index df31fbca74..1a3514162c 100644 --- a/pkg/cli/cmd/group/create/create_test.go +++ b/pkg/cli/cmd/group/create/create_test.go @@ -116,11 +116,7 @@ func Test_Run(t *testing.T) { expected := []any{ output.LogOutput{ - Format: "creating resource group %q in workspace %q...\n", - Params: []any{"testrg", "kind-kind"}, - }, - output.LogOutput{ - Format: "resource group %q created", + Format: "System.Resources/resourceGroups/%s created", Params: []any{"testrg"}, }, } diff --git a/pkg/cli/cmd/group/delete/delete.go b/pkg/cli/cmd/group/delete/delete.go index 005b628822..724b2f0741 100644 --- a/pkg/cli/cmd/group/delete/delete.go +++ b/pkg/cli/cmd/group/delete/delete.go @@ -36,11 +36,8 @@ const ( scopeLocal = "local" // Message templates - msgResourceGroupDeleted = "Resource group %s deleted." - msgResourceGroupNotFound = "Resource group %s does not exist or has already been deleted." - msgResourceGroupNotDeleted = "Resource group %q NOT deleted" - msgDeletingResourceGroup = "Deleting resource group %s...\n" - msgDeletingResourcesWithCount = "Deleting %d resource(s) in group %s..." + msgResourceGroupDeleted = "System.Resources/resourceGroups/%s deleted" + msgResourceGroupNotFound = "System.Resources/resourceGroups/%s not found" ) // NewCommand creates an instance of the command and runner for the `rad group delete` command. @@ -153,17 +150,10 @@ func (r *Runner) Run(ctx context.Context) error { } if !confirmed { - r.Output.LogInfo(msgResourceGroupNotDeleted, r.UCPResourceGroupName) return nil } } - // Show appropriate progress messages with resource count when available - if hasResources { - r.Output.LogInfo(msgDeletingResourcesWithCount, len(resources), r.UCPResourceGroupName) - } - r.Output.LogInfo(msgDeletingResourceGroup, r.UCPResourceGroupName) - // Actually delete the resource group (which will now handle resource deletion internally) deleted, err := client.DeleteResourceGroup(ctx, scopeLocal, r.UCPResourceGroupName) if err != nil { diff --git a/pkg/cli/cmd/group/delete/delete_test.go b/pkg/cli/cmd/group/delete/delete_test.go index 298630166a..891e17963c 100644 --- a/pkg/cli/cmd/group/delete/delete_test.go +++ b/pkg/cli/cmd/group/delete/delete_test.go @@ -97,11 +97,7 @@ func Test_Run(t *testing.T) { skipPrompt: true, expectedOutputs: []any{ output.LogOutput{ - Format: "Deleting resource group %s...\n", - Params: []any{"testrg"}, - }, - output.LogOutput{ - Format: "Resource group %s deleted.", + Format: "System.Resources/resourceGroups/%s deleted", Params: []any{"testrg"}, }, }, @@ -117,15 +113,7 @@ func Test_Run(t *testing.T) { skipPrompt: true, expectedOutputs: []any{ output.LogOutput{ - Format: "Deleting %d resource(s) in group %s...", - Params: []any{2, "testrg"}, - }, - output.LogOutput{ - Format: "Deleting resource group %s...\n", - Params: []any{"testrg"}, - }, - output.LogOutput{ - Format: "Resource group %s deleted.", + Format: "System.Resources/resourceGroups/%s deleted", Params: []any{"testrg"}, }, }, @@ -138,11 +126,7 @@ func Test_Run(t *testing.T) { skipPrompt: true, expectedOutputs: []any{ output.LogOutput{ - Format: "Deleting resource group %s...\n", - Params: []any{"testrg"}, - }, - output.LogOutput{ - Format: "Resource group %s does not exist or has already been deleted.", + Format: "System.Resources/resourceGroups/%s not found", Params: []any{"testrg"}, }, }, @@ -156,28 +140,19 @@ func Test_Run(t *testing.T) { deleteResult: true, expectedOutputs: []any{ output.LogOutput{ - Format: "Deleting resource group %s...\n", - Params: []any{"testrg"}, - }, - output.LogOutput{ - Format: "Resource group %s deleted.", + Format: "System.Resources/resourceGroups/%s deleted", Params: []any{"testrg"}, }, }, }, { - name: "Empty group - user cancels deletion", - confirmation: false, - resources: []generated.GenericResource{}, - promptResponse: prompt.ConfirmNo, - expectedPrompt: "The resource group testrg is empty. Are you sure you want to delete the resource group?", - deleteResult: false, // Won't be called - expectedOutputs: []any{ - output.LogOutput{ - Format: "Resource group %q NOT deleted", - Params: []any{"testrg"}, - }, - }, + name: "Empty group - user cancels deletion", + confirmation: false, + resources: []generated.GenericResource{}, + promptResponse: prompt.ConfirmNo, + expectedPrompt: "The resource group testrg is empty. Are you sure you want to delete the resource group?", + deleteResult: false, // Won't be called + expectedOutputs: nil, }, { name: "Group with resources - user confirms deletion", @@ -191,15 +166,7 @@ func Test_Run(t *testing.T) { deleteResult: true, expectedOutputs: []any{ output.LogOutput{ - Format: "Deleting %d resource(s) in group %s...", - Params: []any{2, "testrg"}, - }, - output.LogOutput{ - Format: "Deleting resource group %s...\n", - Params: []any{"testrg"}, - }, - output.LogOutput{ - Format: "Resource group %s deleted.", + Format: "System.Resources/resourceGroups/%s deleted", Params: []any{"testrg"}, }, }, @@ -210,15 +177,10 @@ func Test_Run(t *testing.T) { resources: []generated.GenericResource{ {Name: new("resource1"), Type: new("Applications.Core/containers")}, }, - promptResponse: prompt.ConfirmNo, - expectedPrompt: "The resource group testrg contains deployed resources. Are you sure you want to delete the resource group and its resources?", - deleteResult: false, // Won't be called - expectedOutputs: []any{ - output.LogOutput{ - Format: "Resource group %q NOT deleted", - Params: []any{"testrg"}, - }, - }, + promptResponse: prompt.ConfirmNo, + expectedPrompt: "The resource group testrg contains deployed resources. Are you sure you want to delete the resource group and its resources?", + deleteResult: false, // Won't be called + expectedOutputs: nil, }, { name: "List resources fails - should not proceed", @@ -238,18 +200,13 @@ func Test_Run(t *testing.T) { expectedOutputs: nil, // No output expected }, { - name: "Delete operation fails", - confirmation: true, - resources: []generated.GenericResource{}, - deleteError: fmt.Errorf("deletion failed"), - skipPrompt: true, - expectedError: fmt.Errorf("deletion failed"), - expectedOutputs: []any{ - output.LogOutput{ - Format: "Deleting resource group %s...\n", - Params: []any{"testrg"}, - }, - }, + name: "Delete operation fails", + confirmation: true, + resources: []generated.GenericResource{}, + deleteError: fmt.Errorf("deletion failed"), + skipPrompt: true, + expectedError: fmt.Errorf("deletion failed"), + expectedOutputs: nil, }, { name: "List returns 404 - group doesn't exist", @@ -260,11 +217,7 @@ func Test_Run(t *testing.T) { deleteResult: false, expectedOutputs: []any{ output.LogOutput{ - Format: "Deleting resource group %s...\n", - Params: []any{"testrg"}, - }, - output.LogOutput{ - Format: "Resource group %s does not exist or has already been deleted.", + Format: "System.Resources/resourceGroups/%s not found", Params: []any{"testrg"}, }, }, @@ -277,11 +230,7 @@ func Test_Run(t *testing.T) { deleteResult: false, expectedOutputs: []any{ output.LogOutput{ - Format: "Deleting resource group %s...\n", - Params: []any{"testrg"}, - }, - output.LogOutput{ - Format: "Resource group %s does not exist or has already been deleted.", + Format: "System.Resources/resourceGroups/%s not found", Params: []any{"testrg"}, }, }, diff --git a/pkg/cli/cmd/recipepack/delete/delete.go b/pkg/cli/cmd/recipepack/delete/delete.go index 7ee4b75937..5d2f7c9c95 100644 --- a/pkg/cli/cmd/recipepack/delete/delete.go +++ b/pkg/cli/cmd/recipepack/delete/delete.go @@ -38,11 +38,9 @@ import ( ) const ( - deleteConfirmationMsg = "Are you sure you want to delete recipe pack '%s'?" - msgDeletingRecipePack = "Deleting recipe pack %s...\n" - msgRecipePackDeleted = "Recipe pack %s deleted." - msgRecipePackNotFound = "Recipe pack %s does not exist or has already been deleted." - msgRecipePackNotDeleted = "Recipe pack %q NOT deleted" + deleteConfirmationMsg = "Are you sure you want to delete recipe pack '%s'?" + msgRecipePackDeleted = "Radius.Core/recipePacks/%s deleted" + msgRecipePackNotFound = "Radius.Core/recipePacks/%s not found" ) // NewCommand creates a new Cobra command for deleting a recipe pack. @@ -139,13 +137,10 @@ func (r *Runner) Run(ctx context.Context) error { } if !confirmed { - r.Output.LogInfo(msgRecipePackNotDeleted, r.RecipePackName) return nil } } - r.Output.LogInfo(msgDeletingRecipePack, r.RecipePackName) - recipePack, err := client.GetRecipePack(ctx, r.RecipePackName) if clients.Is404Error(err) { return clierrors.Message("The recipe pack %q was not found or has been deleted.", r.RecipePackName) diff --git a/pkg/cli/cmd/recipepack/delete/delete_test.go b/pkg/cli/cmd/recipepack/delete/delete_test.go index 109d73d1fb..73edfe94ba 100644 --- a/pkg/cli/cmd/recipepack/delete/delete_test.go +++ b/pkg/cli/cmd/recipepack/delete/delete_test.go @@ -163,7 +163,6 @@ func Test_Run(t *testing.T) { require.Empty(t, capturedEnv.Properties.RecipePacks) require.Equal(t, []any{ - output.LogOutput{Format: msgDeletingRecipePack, Params: []any{packName}}, output.LogOutput{Format: msgRecipePackDeleted, Params: []any{packName}}, }, outputSink.Writes) }) @@ -199,7 +198,6 @@ func Test_Run(t *testing.T) { err := runner.Run(context.Background()) require.NoError(t, err) require.Equal(t, []any{ - output.LogOutput{Format: msgDeletingRecipePack, Params: []any{packName}}, output.LogOutput{Format: msgRecipePackDeleted, Params: []any{packName}}, }, outputSink.Writes) }) @@ -249,7 +247,6 @@ func Test_Run(t *testing.T) { err = runner.Run(context.Background()) require.NoError(t, err) require.Equal(t, []any{ - output.LogOutput{Format: msgDeletingRecipePack, Params: []any{packName}}, output.LogOutput{Format: msgRecipePackDeleted, Params: []any{packName}}, }, outputSink.Writes) }) @@ -277,9 +274,7 @@ func Test_Run(t *testing.T) { err := runner.Run(context.Background()) require.NoError(t, err) - require.Equal(t, []any{ - output.LogOutput{Format: msgRecipePackNotDeleted, Params: []any{packName}}, - }, outputSink.Writes) + require.Empty(t, outputSink.Writes) }) t.Run("get recipe pack returns 404 — returns error", func(t *testing.T) { @@ -461,7 +456,6 @@ func Test_Run(t *testing.T) { err := runner.Run(context.Background()) require.NoError(t, err) require.Equal(t, []any{ - output.LogOutput{Format: msgDeletingRecipePack, Params: []any{packName}}, output.LogOutput{Format: msgRecipePackNotFound, Params: []any{packName}}, }, outputSink.Writes) }) diff --git a/pkg/cli/cmd/resource/create/create.go b/pkg/cli/cmd/resource/create/create.go index 004bacf88c..23db0377da 100644 --- a/pkg/cli/cmd/resource/create/create.go +++ b/pkg/cli/cmd/resource/create/create.go @@ -99,6 +99,7 @@ func (r *Runner) Validate(cmd *cobra.Command, args []string) error { return err } r.Format = format + resourceProviderName, resourceTypeName, err := cli.RequireFullyQualifiedResourceType(args) if err != nil { return err @@ -144,10 +145,12 @@ func (r *Runner) Run(ctx context.Context) error { return err } - err = r.Output.WriteFormatted(r.Format, response, common.GetResourceProviderTableFormat()) - if err != nil { - return err + // For non-default output formats (json), emit the full resource representation + // so callers can script against it. For the default/table format, emit a concise line. + if r.Format == output.FormatJson { + return r.Output.WriteFormatted(r.Format, response, common.GetResourceProviderTableFormat()) } + r.Output.LogInfo("%s/%s created", r.FullyQualifiedResourceTypeName, r.ResourceName) return nil } diff --git a/pkg/cli/cmd/resource/create/create_test.go b/pkg/cli/cmd/resource/create/create_test.go index 1beab31ebb..e4a43048fc 100644 --- a/pkg/cli/cmd/resource/create/create_test.go +++ b/pkg/cli/cmd/resource/create/create_test.go @@ -116,5 +116,11 @@ func Test_Run(t *testing.T) { err := runner.Run(context.Background()) require.NoError(t, err) + require.Equal(t, []any{ + output.LogOutput{ + Format: "%s/%s created", + Params: []any{"Applications.Test/exampleResources", "my-example"}, + }, + }, outputSink.Writes) }) } diff --git a/pkg/cli/cmd/resource/delete/delete.go b/pkg/cli/cmd/resource/delete/delete.go index c4e764f39d..8f8797153f 100644 --- a/pkg/cli/cmd/resource/delete/delete.go +++ b/pkg/cli/cmd/resource/delete/delete.go @@ -156,7 +156,7 @@ func (r *Runner) Run(ctx context.Context) error { environmentID, applicationID, err := r.extractEnvironmentAndApplicationIDs(ctx, client) if clients.Is404Error(err) { - r.Output.LogInfo("Resource '%s' of type '%s' does not exist or has already been deleted", r.ResourceName, r.FullyQualifiedResourceTypeName) + r.Output.LogInfo("%s/%s not found", r.FullyQualifiedResourceTypeName, r.ResourceName) return nil } else if err != nil { return err @@ -182,7 +182,6 @@ func (r *Runner) Run(ctx context.Context) error { return err } if !confirmed { - r.Output.LogInfo("resource %q of type %q NOT deleted", r.ResourceName, r.FullyQualifiedResourceTypeName) return nil } } @@ -193,9 +192,9 @@ func (r *Runner) Run(ctx context.Context) error { } if deleted { - r.Output.LogInfo("Resource deleted") + r.Output.LogInfo("%s/%s deleted", r.FullyQualifiedResourceTypeName, r.ResourceName) } else { - r.Output.LogInfo("Resource '%s' of type '%s' does not exist or has already been deleted", r.ResourceName, r.FullyQualifiedResourceTypeName) + r.Output.LogInfo("%s/%s not found", r.FullyQualifiedResourceTypeName, r.ResourceName) } return nil diff --git a/pkg/cli/cmd/resource/delete/delete_test.go b/pkg/cli/cmd/resource/delete/delete_test.go index af03bedc6e..fd597677bc 100644 --- a/pkg/cli/cmd/resource/delete/delete_test.go +++ b/pkg/cli/cmd/resource/delete/delete_test.go @@ -18,6 +18,7 @@ package delete import ( "context" + "fmt" "net/http" "net/url" "testing" @@ -28,7 +29,10 @@ import ( "github.com/radius-project/radius/pkg/cli/connections" "github.com/radius-project/radius/pkg/cli/framework" "github.com/radius-project/radius/pkg/cli/output" + "github.com/radius-project/radius/pkg/cli/prompt" "github.com/radius-project/radius/pkg/cli/workspaces" + v20231001preview "github.com/radius-project/radius/pkg/corerp/api/v20231001preview" + "github.com/radius-project/radius/pkg/to" "github.com/radius-project/radius/test/radcli" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -132,376 +136,374 @@ func Test_Run(t *testing.T) { require.Error(t, err) require.Equal(t, responseError, err) }) - /* - - t.Run("Success (non-existent)", func(t *testing.T) { - ctrl := gomock.NewController(t) - - appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) - appManagementClient.EXPECT(). - GetResource(gomock.Any(), "Applications.Core/containers", "test-container"). - Return(generated.GenericResource{ - Properties: map[string]interface{}{ - "environment": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/environments/my-test-env", - "application": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/applications/my-test-app", - }, - }, nil). - Times(1) - - appManagementClient.EXPECT(). - DeleteResource(gomock.Any(), "Applications.Core/containers", "test-container"). - Return(false, nil). - Times(1) - - outputSink := &output.MockOutput{} - - runner := &Runner{ - ConnectionFactory: &connections.MockFactory{ApplicationsManagementClient: appManagementClient}, - Output: outputSink, - Workspace: &workspaces.Workspace{}, - FullyQualifiedResourceTypeName: "Applications.Core/containers", - ResourceName: "test-container", - Format: "table", - Confirm: true, - } - - err := runner.Run(context.Background()) - require.NoError(t, err) - - expected := []any{ - output.LogOutput{ - Format: "Resource '%s' of type '%s' does not exist or has already been deleted", - Params: []any{"test-container", "Applications.Core/containers"}, - }, - } - require.Equal(t, expected, outputSink.Writes) - }) - - t.Run("Success (deleted)", func(t *testing.T) { - ctrl := gomock.NewController(t) - - appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) - appManagementClient.EXPECT(). - GetResource(gomock.Any(), "Applications.Core/containers", "test-container"). - Return(generated.GenericResource{ - Properties: map[string]interface{}{ - "environment": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/environments/my-test-env", - "application": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/applications/my-test-app", - }, - }, nil). - Times(1) - appManagementClient.EXPECT(). - DeleteResource(gomock.Any(), "Applications.Core/containers", "test-container"). - Return(true, nil). - Times(1) - - outputSink := &output.MockOutput{} - - runner := &Runner{ - ConnectionFactory: &connections.MockFactory{ApplicationsManagementClient: appManagementClient}, - Output: outputSink, - Workspace: &workspaces.Workspace{}, - FullyQualifiedResourceTypeName: "Applications.Core/containers", - ResourceName: "test-container", - Format: "table", - Confirm: true, - } - - err := runner.Run(context.Background()) - require.NoError(t, err) - - expected := []any{ - output.LogOutput{ - Format: "Resource deleted", - }, - } - require.Equal(t, expected, outputSink.Writes) - }) - - t.Run("Success: Prompt Confirmed (case 1: application-scoped standard resource)", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - promptMock := prompt.NewMockInterface(ctrl) - promptMock.EXPECT(). - GetListInput([]string{prompt.ConfirmNo, prompt.ConfirmYes}, fmt.Sprintf(deleteConfirmationWithApplication, "test-container", "Applications.Core/containers", "my-test-app", "my-test-env")). - Return(prompt.ConfirmYes, nil). - Times(1) - - appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) - appManagementClient.EXPECT(). - GetResource(gomock.Any(), "Applications.Core/containers", "test-container"). - Return(generated.GenericResource{ - Properties: map[string]interface{}{ - "environment": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/environments/my-test-env", - "application": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/applications/my-test-app", - }, - }, nil). - Times(1) - appManagementClient.EXPECT(). - DeleteResource(gomock.Any(), "Applications.Core/containers", "test-container"). - 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}, - Output: outputSink, - Workspace: workspace, - FullyQualifiedResourceTypeName: "Applications.Core/containers", - ResourceName: "test-container", - Format: "table", - InputPrompter: promptMock, - } - - err := runner.Run(context.Background()) - require.NoError(t, err) - - expected := []any{ - output.LogOutput{ - Format: "Resource deleted", - }, - } - - require.Equal(t, expected, outputSink.Writes) - }) - - t.Run("Success: Prompt Confirmed (case 2: environment-scoped standard resource)", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - promptMock := prompt.NewMockInterface(ctrl) - promptMock.EXPECT(). - GetListInput([]string{prompt.ConfirmNo, prompt.ConfirmYes}, fmt.Sprintf(deleteConfirmationWithoutApplication, "test-container", "Applications.Core/containers", "my-test-env")). - Return(prompt.ConfirmYes, nil). - Times(1) - - appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) - appManagementClient.EXPECT(). - GetResource(gomock.Any(), "Applications.Core/containers", "test-container"). - Return(generated.GenericResource{ - Properties: map[string]interface{}{ - "environment": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/environments/my-test-env", - }, - }, nil). - Times(1) - appManagementClient.EXPECT(). - DeleteResource(gomock.Any(), "Applications.Core/containers", "test-container"). - 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}, - Output: outputSink, - Workspace: workspace, - FullyQualifiedResourceTypeName: "Applications.Core/containers", - ResourceName: "test-container", - Format: "table", - InputPrompter: promptMock, - } - - err := runner.Run(context.Background()) - require.NoError(t, err) - - expected := []any{ - output.LogOutput{ - Format: "Resource deleted", + + t.Run("Success (non-existent)", func(t *testing.T) { + ctrl := gomock.NewController(t) + + appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) + appManagementClient.EXPECT(). + GetResource(gomock.Any(), "Applications.Core/containers", "test-container"). + Return(generated.GenericResource{ + Properties: map[string]interface{}{ + "environment": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/environments/my-test-env", + "application": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/applications/my-test-app", }, - } - - require.Equal(t, expected, outputSink.Writes) - }) - - // NOTE: this case requires an extra lookup to get the environment name. - t.Run("Success: Prompt Confirmed (case 3: application-scoped core resource)", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - promptMock := prompt.NewMockInterface(ctrl) - promptMock.EXPECT(). - GetListInput([]string{prompt.ConfirmNo, prompt.ConfirmYes}, fmt.Sprintf(deleteConfirmationWithApplication, "test-container", "Applications.Core/containers", "my-test-app", "my-test-env")). - Return(prompt.ConfirmYes, nil). - Times(1) - - appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) - appManagementClient.EXPECT(). - GetResource(gomock.Any(), "Applications.Core/containers", "test-container"). - Return(generated.GenericResource{ - Properties: map[string]interface{}{ - "application": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/applications/my-test-app", - }, - }, nil). - Times(1) - - appManagementClient.EXPECT(). - GetApplication(gomock.Any(), "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/applications/my-test-app"). - Return(v20231001preview.ApplicationResource{ - Properties: &v20231001preview.ApplicationProperties{ - Environment: to.Ptr("/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/environments/my-test-env"), - }, - }, nil). - Times(1) - appManagementClient.EXPECT(). - DeleteResource(gomock.Any(), "Applications.Core/containers", "test-container"). - Return(true, nil). - Times(1) - - workspace := &workspaces.Workspace{ - Connection: map[string]any{ - "kind": "kubernetes", - "context": "kind-kind", + }, nil). + Times(1) + + appManagementClient.EXPECT(). + DeleteResource(gomock.Any(), "Applications.Core/containers", "test-container", false). + Return(false, nil). + Times(1) + + outputSink := &output.MockOutput{} + + runner := &Runner{ + ConnectionFactory: &connections.MockFactory{ApplicationsManagementClient: appManagementClient}, + Output: outputSink, + Workspace: &workspaces.Workspace{}, + FullyQualifiedResourceTypeName: "Applications.Core/containers", + ResourceName: "test-container", + Format: "table", + Confirm: true, + } + + err := runner.Run(context.Background()) + require.NoError(t, err) + + expected := []any{ + output.LogOutput{ + Format: "%s/%s not found", + Params: []any{"Applications.Core/containers", "test-container"}, + }, + } + require.Equal(t, expected, outputSink.Writes) + }) + + t.Run("Success (deleted)", func(t *testing.T) { + ctrl := gomock.NewController(t) + + appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) + appManagementClient.EXPECT(). + GetResource(gomock.Any(), "Applications.Core/containers", "test-container"). + Return(generated.GenericResource{ + Properties: map[string]interface{}{ + "environment": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/environments/my-test-env", + "application": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/applications/my-test-app", }, - Name: "kind-kind", - Scope: "/planes/radius/local/resourceGroups/test-group", - } - - outputSink := &output.MockOutput{} - runner := &Runner{ - ConnectionFactory: &connections.MockFactory{ApplicationsManagementClient: appManagementClient}, - Output: outputSink, - Workspace: workspace, - FullyQualifiedResourceTypeName: "Applications.Core/containers", - ResourceName: "test-container", - Format: "table", - InputPrompter: promptMock, - } - - err := runner.Run(context.Background()) - require.NoError(t, err) - - expected := []any{ - output.LogOutput{ - Format: "Resource deleted", + }, nil). + Times(1) + appManagementClient.EXPECT(). + DeleteResource(gomock.Any(), "Applications.Core/containers", "test-container", false). + Return(true, nil). + Times(1) + + outputSink := &output.MockOutput{} + + runner := &Runner{ + ConnectionFactory: &connections.MockFactory{ApplicationsManagementClient: appManagementClient}, + Output: outputSink, + Workspace: &workspaces.Workspace{}, + FullyQualifiedResourceTypeName: "Applications.Core/containers", + ResourceName: "test-container", + Format: "table", + Confirm: true, + } + + err := runner.Run(context.Background()) + require.NoError(t, err) + + expected := []any{ + output.LogOutput{ + Format: "%s/%s deleted", + Params: []any{"Applications.Core/containers", "test-container"}, + }, + } + require.Equal(t, expected, outputSink.Writes) + }) + + t.Run("Success: Prompt Confirmed (case 1: application-scoped standard resource)", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + promptMock := prompt.NewMockInterface(ctrl) + promptMock.EXPECT(). + GetListInput([]string{prompt.ConfirmNo, prompt.ConfirmYes}, fmt.Sprintf(deleteConfirmationWithApplication, "test-container", "Applications.Core/containers", "my-test-app", "my-test-env")). + Return(prompt.ConfirmYes, nil). + Times(1) + + appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) + appManagementClient.EXPECT(). + GetResource(gomock.Any(), "Applications.Core/containers", "test-container"). + Return(generated.GenericResource{ + Properties: map[string]interface{}{ + "environment": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/environments/my-test-env", + "application": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/applications/my-test-app", }, - } - - require.Equal(t, expected, outputSink.Writes) - }) - - t.Run("Success: Prompt Confirmed (case 4: no application or environment)", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - promptMock := prompt.NewMockInterface(ctrl) - promptMock.EXPECT(). - GetListInput([]string{prompt.ConfirmNo, prompt.ConfirmYes}, fmt.Sprintf(deleteConfirmationWithoutApplicationOrEnvironment, "test-container", "Applications.Core/containers")). - Return(prompt.ConfirmYes, nil). - Times(1) - - appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) - appManagementClient.EXPECT(). - GetResource(gomock.Any(), "Applications.Core/containers", "test-container"). - Return(generated.GenericResource{ - Properties: map[string]interface{}{}, - }, nil). - Times(1) - - appManagementClient.EXPECT(). - DeleteResource(gomock.Any(), "Applications.Core/containers", "test-container"). - Return(true, nil). - Times(1) - - workspace := &workspaces.Workspace{ - Connection: map[string]any{ - "kind": "kubernetes", - "context": "kind-kind", + }, nil). + Times(1) + appManagementClient.EXPECT(). + DeleteResource(gomock.Any(), "Applications.Core/containers", "test-container", false). + 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}, + Output: outputSink, + Workspace: workspace, + FullyQualifiedResourceTypeName: "Applications.Core/containers", + ResourceName: "test-container", + Format: "table", + InputPrompter: promptMock, + } + + err := runner.Run(context.Background()) + require.NoError(t, err) + + expected := []any{ + output.LogOutput{ + Format: "%s/%s deleted", + Params: []any{"Applications.Core/containers", "test-container"}, + }, + } + + require.Equal(t, expected, outputSink.Writes) + }) + + t.Run("Success: Prompt Confirmed (case 2: environment-scoped standard resource)", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + promptMock := prompt.NewMockInterface(ctrl) + promptMock.EXPECT(). + GetListInput([]string{prompt.ConfirmNo, prompt.ConfirmYes}, fmt.Sprintf(deleteConfirmationWithoutApplication, "test-container", "Applications.Core/containers", "my-test-env")). + Return(prompt.ConfirmYes, nil). + Times(1) + + appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) + appManagementClient.EXPECT(). + GetResource(gomock.Any(), "Applications.Core/containers", "test-container"). + Return(generated.GenericResource{ + Properties: map[string]interface{}{ + "environment": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/environments/my-test-env", }, - Name: "kind-kind", - Scope: "/planes/radius/local/resourceGroups/test-group", - } - outputSink := &output.MockOutput{} - runner := &Runner{ - ConnectionFactory: &connections.MockFactory{ApplicationsManagementClient: appManagementClient}, - Output: outputSink, - Workspace: workspace, - FullyQualifiedResourceTypeName: "Applications.Core/containers", - ResourceName: "test-container", - Format: "table", - InputPrompter: promptMock, - } - - err := runner.Run(context.Background()) - require.NoError(t, err) - - expected := []any{ - output.LogOutput{ - Format: "Resource deleted", + }, nil). + Times(1) + appManagementClient.EXPECT(). + DeleteResource(gomock.Any(), "Applications.Core/containers", "test-container", false). + 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}, + Output: outputSink, + Workspace: workspace, + FullyQualifiedResourceTypeName: "Applications.Core/containers", + ResourceName: "test-container", + Format: "table", + InputPrompter: promptMock, + } + + err := runner.Run(context.Background()) + require.NoError(t, err) + + expected := []any{ + output.LogOutput{ + Format: "%s/%s deleted", + Params: []any{"Applications.Core/containers", "test-container"}, + }, + } + + require.Equal(t, expected, outputSink.Writes) + }) + + // NOTE: this case requires an extra lookup to get the environment name. + t.Run("Success: Prompt Confirmed (case 3: application-scoped core resource)", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + promptMock := prompt.NewMockInterface(ctrl) + promptMock.EXPECT(). + GetListInput([]string{prompt.ConfirmNo, prompt.ConfirmYes}, fmt.Sprintf(deleteConfirmationWithApplication, "test-container", "Applications.Core/containers", "my-test-app", "my-test-env")). + Return(prompt.ConfirmYes, nil). + Times(1) + + appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) + appManagementClient.EXPECT(). + GetResource(gomock.Any(), "Applications.Core/containers", "test-container"). + Return(generated.GenericResource{ + Properties: map[string]interface{}{ + "application": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/applications/my-test-app", }, - } - - require.Equal(t, expected, outputSink.Writes) - }) - - t.Run("Success: Prompt Cancelled", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) - appManagementClient.EXPECT(). - GetResource(gomock.Any(), "Applications.Core/containers", "test-container"). - Return(generated.GenericResource{ - Properties: map[string]interface{}{ - "environment": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/environments/my-test-env", - "application": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/applications/my-test-app", - }, - }, nil). - Times(1) - - promptMock := prompt.NewMockInterface(ctrl) - promptMock.EXPECT(). - GetListInput([]string{prompt.ConfirmNo, prompt.ConfirmYes}, fmt.Sprintf(deleteConfirmationWithApplication, "test-container", "Applications.Core/containers", "my-test-app", "my-test-env")). - Return(prompt.ConfirmNo, nil). - Times(1) - - workspace := &workspaces.Workspace{ - Connection: map[string]any{ - "kind": "kubernetes", - "context": "kind-kind", + }, nil). + Times(1) + + appManagementClient.EXPECT(). + GetApplication(gomock.Any(), "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/applications/my-test-app"). + Return(v20231001preview.ApplicationResource{ + Properties: &v20231001preview.ApplicationProperties{ + Environment: to.Ptr("/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/environments/my-test-env"), }, - 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, - FullyQualifiedResourceTypeName: "Applications.Core/containers", - ResourceName: "test-container", - } - - err := runner.Run(context.Background()) - require.NoError(t, err) - - expected := []any{ - output.LogOutput{ - Format: "resource %q of type %q NOT deleted", - Params: []any{"test-container", "Applications.Core/containers"}, + }, nil). + Times(1) + appManagementClient.EXPECT(). + DeleteResource(gomock.Any(), "Applications.Core/containers", "test-container", false). + 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}, + Output: outputSink, + Workspace: workspace, + FullyQualifiedResourceTypeName: "Applications.Core/containers", + ResourceName: "test-container", + Format: "table", + InputPrompter: promptMock, + } + + err := runner.Run(context.Background()) + require.NoError(t, err) + + expected := []any{ + output.LogOutput{ + Format: "%s/%s deleted", + Params: []any{"Applications.Core/containers", "test-container"}, + }, + } + + require.Equal(t, expected, outputSink.Writes) + }) + + t.Run("Success: Prompt Confirmed (case 4: no application or environment)", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + promptMock := prompt.NewMockInterface(ctrl) + promptMock.EXPECT(). + GetListInput([]string{prompt.ConfirmNo, prompt.ConfirmYes}, fmt.Sprintf(deleteConfirmationWithoutApplicationOrEnvironment, "test-container", "Applications.Core/containers")). + Return(prompt.ConfirmYes, nil). + Times(1) + + appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) + appManagementClient.EXPECT(). + GetResource(gomock.Any(), "Applications.Core/containers", "test-container"). + Return(generated.GenericResource{ + Properties: map[string]interface{}{}, + }, nil). + Times(1) + + appManagementClient.EXPECT(). + DeleteResource(gomock.Any(), "Applications.Core/containers", "test-container", false). + 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}, + Output: outputSink, + Workspace: workspace, + FullyQualifiedResourceTypeName: "Applications.Core/containers", + ResourceName: "test-container", + Format: "table", + InputPrompter: promptMock, + } + + err := runner.Run(context.Background()) + require.NoError(t, err) + + expected := []any{ + output.LogOutput{ + Format: "%s/%s deleted", + Params: []any{"Applications.Core/containers", "test-container"}, + }, + } + + require.Equal(t, expected, outputSink.Writes) + }) + + t.Run("Success: Prompt Cancelled", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + appManagementClient := clients.NewMockApplicationsManagementClient(ctrl) + appManagementClient.EXPECT(). + GetResource(gomock.Any(), "Applications.Core/containers", "test-container"). + Return(generated.GenericResource{ + Properties: map[string]interface{}{ + "environment": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/environments/my-test-env", + "application": "/planes/radius/local/resourceGroups/test-group/providers/Applications.Core/applications/my-test-app", }, - } - require.Equal(t, expected, outputSink.Writes) - })*/ + }, nil). + Times(1) + + promptMock := prompt.NewMockInterface(ctrl) + promptMock.EXPECT(). + GetListInput([]string{prompt.ConfirmNo, prompt.ConfirmYes}, fmt.Sprintf(deleteConfirmationWithApplication, "test-container", "Applications.Core/containers", "my-test-app", "my-test-env")). + Return(prompt.ConfirmNo, 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, + FullyQualifiedResourceTypeName: "Applications.Core/containers", + ResourceName: "test-container", + } + + err := runner.Run(context.Background()) + require.NoError(t, err) + + require.Empty(t, outputSink.Writes) + }) t.Run("Success (force deleted)", func(t *testing.T) { ctrl := gomock.NewController(t) @@ -542,11 +544,12 @@ func Test_Run(t *testing.T) { Format: "WARNING: Force deleting a resource in a non-terminal state may leave orphaned external resources that require manual cleanup.", }, output.LogOutput{ - Format: "Resource deleted", + Format: "%s/%s deleted", + Params: []any{"Applications.Core/containers", "test-container"}, }, } require.Equal(t, expected, outputSink.Writes) }) }) -} \ No newline at end of file +} diff --git a/pkg/cli/cmd/resourceprovider/create/create.go b/pkg/cli/cmd/resourceprovider/create/create.go index befc928e8d..e6692d0b87 100644 --- a/pkg/cli/cmd/resourceprovider/create/create.go +++ b/pkg/cli/cmd/resourceprovider/create/create.go @@ -22,7 +22,6 @@ import ( aztoken "github.com/radius-project/radius/pkg/azure/tokencredentials" "github.com/radius-project/radius/pkg/cli" "github.com/radius-project/radius/pkg/cli/cmd/commonflags" - "github.com/radius-project/radius/pkg/cli/cmd/resourceprovider/common" "github.com/radius-project/radius/pkg/cli/framework" "github.com/radius-project/radius/pkg/cli/manifest" "github.com/radius-project/radius/pkg/cli/output" @@ -77,18 +76,13 @@ type Runner struct { ResourceProviderManifestFilePath string ResourceProvider *manifest.ResourceProvider - Logger func(format string, args ...any) } // NewRunner creates an instance of the runner for the `rad resource-provider create` command. func NewRunner(factory framework.Factory) *Runner { - output := factory.GetOutput() return &Runner{ ConfigHolder: factory.GetConfigHolder(), - Output: output, - Logger: func(format string, args ...any) { - output.LogInfo(format, args...) - }, + Output: factory.GetOutput(), } } @@ -126,23 +120,13 @@ func (r *Runner) Run(ctx context.Context) error { } } - // Proceed with registering manifests - if err := manifest.RegisterFile(ctx, r.UCPClientFactory, "local", r.ResourceProviderManifestFilePath, r.Logger); err != nil { - return err - } - - response, err := r.UCPClientFactory.NewResourceProvidersClient().Get(ctx, "local", r.ResourceProvider.Namespace, nil) - if err != nil { - return err - } - - r.Output.LogInfo("") - - err = r.Output.WriteFormatted(r.Format, response, common.GetResourceProviderTableFormat()) - if err != nil { + // Proceed with registering manifests. Use a nil logger to suppress verbose + // progress messages; the concise success line is emitted below. + if err := manifest.RegisterFile(ctx, r.UCPClientFactory, "local", r.ResourceProviderManifestFilePath, nil); err != nil { return err } + r.Output.LogInfo("System.Resources/resourceProviders/%s created", r.ResourceProvider.Namespace) return nil } diff --git a/pkg/cli/cmd/resourceprovider/create/create_test.go b/pkg/cli/cmd/resourceprovider/create/create_test.go index 35db8b574f..8f394f8230 100644 --- a/pkg/cli/cmd/resourceprovider/create/create_test.go +++ b/pkg/cli/cmd/resourceprovider/create/create_test.go @@ -17,9 +17,7 @@ limitations under the License. package create import ( - "bytes" "context" - "fmt" "testing" "github.com/radius-project/radius/pkg/cli/framework" @@ -73,32 +71,50 @@ func Test_Run(t *testing.T) { resourceProviderData, err := manifest.ReadFile("testdata/valid.yaml") require.NoError(t, err) - expectedResourceType := "testResources" - expectedAPIVersion := "2025-01-01-preview" - clientFactory, err := manifest.NewTestClientFactory(manifest.WithResourceProviderServerNoError) require.NoError(t, err) - var logBuffer bytes.Buffer - logger := func(format string, args ...any) { - fmt.Fprintf(&logBuffer, format+"\n", args...) - } + outputSink := &output.MockOutput{} runner := &Runner{ UCPClientFactory: clientFactory, - Output: &output.MockOutput{}, + Output: outputSink, Workspace: &workspaces.Workspace{}, ResourceProvider: resourceProviderData, Format: "table", - Logger: logger, ResourceProviderManifestFilePath: "testdata/valid.yaml", } err = runner.Run(context.Background()) require.NoError(t, err) - logOutput := logBuffer.String() - require.Contains(t, logOutput, fmt.Sprintf("Creating resource type %s/%s", resourceProviderData.Namespace, expectedResourceType)) - require.Contains(t, logOutput, fmt.Sprintf("Creating API Version %s/%s@%s", resourceProviderData.Namespace, expectedResourceType, expectedAPIVersion)) + require.Equal(t, []any{ + output.LogOutput{ + Format: "System.Resources/resourceProviders/%s created", + Params: []any{resourceProviderData.Namespace}, + }, + }, outputSink.Writes) + }) + + t.Run("Failure: RegisterFile returns error", func(t *testing.T) { + resourceProviderData, err := manifest.ReadFile("testdata/valid.yaml") + require.NoError(t, err) + + clientFactory, err := manifest.NewTestClientFactory(manifest.WithResourceProviderServerInternalError) + require.NoError(t, err) + + outputSink := &output.MockOutput{} + runner := &Runner{ + UCPClientFactory: clientFactory, + Output: outputSink, + Workspace: &workspaces.Workspace{}, + ResourceProvider: resourceProviderData, + Format: "table", + ResourceProviderManifestFilePath: "testdata/valid.yaml", + } + + err = runner.Run(context.Background()) + require.Error(t, err) + require.Empty(t, outputSink.Writes) }) } diff --git a/pkg/cli/cmd/resourceprovider/delete/delete.go b/pkg/cli/cmd/resourceprovider/delete/delete.go index 248f5b7e65..1aefb01fcc 100644 --- a/pkg/cli/cmd/resourceprovider/delete/delete.go +++ b/pkg/cli/cmd/resourceprovider/delete/delete.go @@ -140,9 +140,9 @@ func (r *Runner) Run(ctx context.Context) error { } if deleted { - r.Output.LogInfo("Resource provider %q deleted.", r.ResourceProviderNamespace) + r.Output.LogInfo("System.Resources/resourceProviders/%s deleted", r.ResourceProviderNamespace) } else { - r.Output.LogInfo("Resource provider %q does not exist or has already been deleted.", r.ResourceProviderNamespace) + r.Output.LogInfo("System.Resources/resourceProviders/%s not found", r.ResourceProviderNamespace) } return nil diff --git a/pkg/cli/cmd/resourceprovider/delete/delete_test.go b/pkg/cli/cmd/resourceprovider/delete/delete_test.go index d427343db9..cec586691d 100644 --- a/pkg/cli/cmd/resourceprovider/delete/delete_test.go +++ b/pkg/cli/cmd/resourceprovider/delete/delete_test.go @@ -95,7 +95,7 @@ func Test_Run(t *testing.T) { expected := []any{ output.LogOutput{ - Format: "Resource provider %q deleted.", + Format: "System.Resources/resourceProviders/%s deleted", Params: []any{"Applications.Test"}, }, } @@ -136,7 +136,7 @@ func Test_Run(t *testing.T) { expected := []any{ output.LogOutput{ - Format: "Resource provider %q does not exist or has already been deleted.", + Format: "System.Resources/resourceProviders/%s not found", Params: []any{"Applications.Test"}, }, } @@ -183,7 +183,7 @@ func Test_Run(t *testing.T) { expected := []any{ output.LogOutput{ - Format: "Resource provider %q deleted.", + Format: "System.Resources/resourceProviders/%s deleted", Params: []any{"Applications.Test"}, }, } diff --git a/pkg/cli/cmd/resourcetype/create/create.go b/pkg/cli/cmd/resourcetype/create/create.go index da631f0052..635844db7c 100644 --- a/pkg/cli/cmd/resourcetype/create/create.go +++ b/pkg/cli/cmd/resourcetype/create/create.go @@ -32,9 +32,7 @@ import ( ) const ( - defaultPlaneName = "local" - msgNoResourceTypeNameProvided = "No resource type name provided. Creating all resource types in the manifest." - msgAllResourceTypesCreated = "All resource types in the manifest created successfully" + defaultPlaneName = "local" ) // NewCommand creates an instance of the `rad resource-type create` command and runner. @@ -89,7 +87,6 @@ type Runner struct { ResourceProviderManifestFilePath string ResourceProvider *manifest.ResourceProvider ResourceTypeName string - Logger func(format string, args ...any) } // NewRunner creates an instance of the runner for the `rad resource-type create` command. @@ -97,9 +94,6 @@ func NewRunner(factory framework.Factory) *Runner { return &Runner{ ConfigHolder: factory.GetConfigHolder(), Output: factory.GetOutput(), - Logger: func(format string, args ...any) { - output.LogInfo(format, args...) - }, } } @@ -149,7 +143,6 @@ func (r *Runner) Run(ctx context.Context) error { } if r.ResourceTypeName == "" { - r.Output.LogInfo(msgNoResourceTypeNameProvided) return r.registerTypes(ctx, nil) // Register all types } @@ -158,8 +151,9 @@ func (r *Runner) Run(ctx context.Context) error { // registerTypes registers the specified resource types (or all types if typeNames is nil) func (r *Runner) registerTypes(ctx context.Context, typeNames []string) error { - // Always ensure the resource provider exists first - err := manifest.EnsureResourceProviderExists(ctx, r.UCPClientFactory, defaultPlaneName, *r.ResourceProvider, r.Logger) + // Always ensure the resource provider exists first. Use a nil logger to suppress + // verbose progress messages; the concise success line is emitted below. + err := manifest.EnsureResourceProviderExists(ctx, r.UCPClientFactory, defaultPlaneName, *r.ResourceProvider, nil) if err != nil { return err } @@ -176,19 +170,13 @@ func (r *Runner) registerTypes(ctx context.Context, typeNames []string) error { } } - // Register each type individually using the unified approach + // Register each type individually and emit a single concise line per type. for _, typeName := range typesToRegister { - err = manifest.RegisterType(ctx, r.UCPClientFactory, defaultPlaneName, r.ResourceProviderManifestFilePath, typeName, r.Logger) + err = manifest.RegisterType(ctx, r.UCPClientFactory, defaultPlaneName, r.ResourceProviderManifestFilePath, typeName, nil) if err != nil { return err } - } - - // Provide appropriate success message - if len(typesToRegister) == 1 { - // Single type - success message already logged by RegisterType - } else { - r.Output.LogInfo(msgAllResourceTypesCreated) + r.Output.LogInfo("%s/%s created", r.ResourceProvider.Namespace, typeName) } return nil diff --git a/pkg/cli/cmd/resourcetype/create/create_test.go b/pkg/cli/cmd/resourcetype/create/create_test.go index c56fb66547..f4aafb5495 100644 --- a/pkg/cli/cmd/resourcetype/create/create_test.go +++ b/pkg/cli/cmd/resourcetype/create/create_test.go @@ -17,9 +17,7 @@ limitations under the License. package create import ( - "bytes" "context" - "fmt" "testing" "github.com/radius-project/radius/pkg/cli/framework" @@ -73,11 +71,6 @@ func Test_Run(t *testing.T) { clientFactory, err := manifest.NewTestClientFactory(manifest.WithResourceProviderServerNoError) require.NoError(t, err) - var logBuffer bytes.Buffer - logger := func(format string, args ...any) { - fmt.Fprintf(&logBuffer, format+"\n", args...) - } - outputSink := &output.MockOutput{} runner := &Runner{ UCPClientFactory: clientFactory, @@ -85,7 +78,6 @@ func Test_Run(t *testing.T) { Workspace: &workspaces.Workspace{}, ResourceProvider: resourceProviderData, Format: "table", - Logger: logger, ResourceProviderManifestFilePath: "testdata/valid.yaml", ResourceTypeName: "testResources", } @@ -93,9 +85,10 @@ func Test_Run(t *testing.T) { err = runner.Run(context.Background()) require.NoError(t, err) - // Verify RegisterType was called (should see specific log messages) - logOutput := logBuffer.String() - require.Contains(t, logOutput, fmt.Sprintf("Creating resource type %s/%s", runner.ResourceProvider.Namespace, "testResources")) + require.Contains(t, outputSink.Writes, output.LogOutput{ + Format: "%s/%s created", + Params: []any{runner.ResourceProvider.Namespace, "testResources"}, + }) }) t.Run("No resource type name provided - registers entire manifest", func(t *testing.T) { @@ -108,11 +101,6 @@ func Test_Run(t *testing.T) { clientFactory, err := manifest.NewTestClientFactory(manifest.WithResourceProviderServerNoError) require.NoError(t, err) - var logBuffer bytes.Buffer - logger := func(format string, args ...any) { - fmt.Fprintf(&logBuffer, format+"\n", args...) - } - outputSink := &output.MockOutput{} runner := &Runner{ UCPClientFactory: clientFactory, @@ -120,7 +108,6 @@ func Test_Run(t *testing.T) { Workspace: &workspaces.Workspace{}, ResourceProvider: resourceProviderData, Format: "table", - Logger: logger, ResourceProviderManifestFilePath: "testdata/valid.yaml", ResourceTypeName: "", // Empty resource type name } @@ -128,17 +115,15 @@ func Test_Run(t *testing.T) { err = runner.Run(context.Background()) require.NoError(t, err) - // Verify the correct log message is output - expectedLog := output.LogOutput{ - Format: "No resource type name provided. Creating all resource types in the manifest.", - Params: nil, - } - require.Contains(t, outputSink.Writes, expectedLog, "Expected log message for no resource type name provided") - - // Verify RegisterResourceProvider was called - logOutput := logBuffer.String() - require.Contains(t, logOutput, fmt.Sprintf("Creating resource type %s/%s", runner.ResourceProvider.Namespace, "testResources")) - require.Contains(t, logOutput, fmt.Sprintf("Creating resource type %s/%s", runner.ResourceProvider.Namespace, "prodResources")) + // Verify the concise success log lines for both resource types are emitted + require.Contains(t, outputSink.Writes, output.LogOutput{ + Format: "%s/%s created", + Params: []any{runner.ResourceProvider.Namespace, "testResources"}, + }) + require.Contains(t, outputSink.Writes, output.LogOutput{ + Format: "%s/%s created", + Params: []any{runner.ResourceProvider.Namespace, "prodResources"}, + }) }) t.Run("Resource provider does not exist - registers resource provider with single type", func(t *testing.T) { @@ -153,11 +138,6 @@ func Test_Run(t *testing.T) { clientFactory, err := manifest.NewTestClientFactory(manifest.WithResourceProviderServerNotFoundError) require.NoError(t, err) - var logBuffer bytes.Buffer - logger := func(format string, args ...any) { - fmt.Fprintf(&logBuffer, format+"\n", args...) - } - outputSink := &output.MockOutput{} runner := &Runner{ UCPClientFactory: clientFactory, @@ -165,17 +145,21 @@ func Test_Run(t *testing.T) { Workspace: &workspaces.Workspace{}, ResourceProvider: resourceProviderData, Format: "table", - Logger: logger, ResourceProviderManifestFilePath: "testdata/valid.yaml", ResourceTypeName: expectedResourceType, } _ = runner.Run(context.Background()) - // Verify RegisterResourceProvider was called with only the specified resource type - logOutput := logBuffer.String() - require.Contains(t, logOutput, fmt.Sprintf("Creating resource type %s/%s", runner.ResourceProvider.Namespace, "testResources")) - require.NotContains(t, logOutput, fmt.Sprintf("Creating resource type %s/%s", runner.ResourceProvider.Namespace, "prodResources")) + // Verify only the specified resource type produced a success line. + require.Contains(t, outputSink.Writes, output.LogOutput{ + Format: "%s/%s created", + Params: []any{runner.ResourceProvider.Namespace, "testResources"}, + }) + require.NotContains(t, outputSink.Writes, output.LogOutput{ + Format: "%s/%s created", + Params: []any{runner.ResourceProvider.Namespace, "prodResources"}, + }) }) t.Run("Get Resource provider Internal Error", func(t *testing.T) { ctrl := gomock.NewController(t) @@ -189,18 +173,12 @@ func Test_Run(t *testing.T) { clientFactory, err := manifest.NewTestClientFactory(manifest.WithResourceProviderServerInternalError) require.NoError(t, err) - var logBuffer bytes.Buffer - logger := func(format string, args ...any) { - fmt.Fprintf(&logBuffer, format+"\n", args...) - } - runner := &Runner{ UCPClientFactory: clientFactory, Output: &output.MockOutput{}, Workspace: &workspaces.Workspace{}, ResourceProvider: resourceProviderData, Format: "table", - Logger: logger, ResourceProviderManifestFilePath: "testdata/valid.yaml", ResourceTypeName: expectedResourceType, } diff --git a/pkg/cli/cmd/resourcetype/delete/delete.go b/pkg/cli/cmd/resourcetype/delete/delete.go index 0c3533029a..e7fd7c5d2d 100644 --- a/pkg/cli/cmd/resourcetype/delete/delete.go +++ b/pkg/cli/cmd/resourcetype/delete/delete.go @@ -145,9 +145,9 @@ func (r *Runner) Run(ctx context.Context) error { } if deleted { - r.Output.LogInfo("Resource type %q deleted.", r.ResourceTypeName) + r.Output.LogInfo("%s deleted", r.ResourceTypeName) } else { - r.Output.LogInfo("Resource type %q does not exist or has already been deleted.", r.ResourceTypeName) + r.Output.LogInfo("%s not found", r.ResourceTypeName) } return nil diff --git a/pkg/cli/cmd/resourcetype/delete/delete_test.go b/pkg/cli/cmd/resourcetype/delete/delete_test.go index 331d2b560d..686fd3bb4a 100644 --- a/pkg/cli/cmd/resourcetype/delete/delete_test.go +++ b/pkg/cli/cmd/resourcetype/delete/delete_test.go @@ -103,7 +103,7 @@ func Test_Run(t *testing.T) { expected := []any{ output.LogOutput{ - Format: "Resource type %q deleted.", + Format: "%s deleted", Params: []any{"Applications.Test/testResources"}, }, } @@ -146,7 +146,7 @@ func Test_Run(t *testing.T) { expected := []any{ output.LogOutput{ - Format: "Resource type %q does not exist or has already been deleted.", + Format: "%s not found", Params: []any{"Applications.Test/testResources"}, }, } @@ -195,7 +195,7 @@ func Test_Run(t *testing.T) { expected := []any{ output.LogOutput{ - Format: "Resource type %q deleted.", + Format: "%s deleted", Params: []any{"Applications.Test/testResources"}, }, } diff --git a/pkg/cli/cmd/workspace/create/create.go b/pkg/cli/cmd/workspace/create/create.go index 64e77f154b..fe56291b75 100644 --- a/pkg/cli/cmd/workspace/create/create.go +++ b/pkg/cli/cmd/workspace/create/create.go @@ -208,12 +208,11 @@ func (r *Runner) Validate(cmd *cobra.Command, args []string) error { // Run creates a workspace and sets it as the current workspace, returning an error if any occurs during the process." func (r *Runner) Run(ctx context.Context) error { - r.Output.LogInfo("Creating workspace...") err := r.ConfigFileInterface.EditWorkspaces(ctx, r.ConfigHolder.Config, r.Workspace) if err != nil { return err } - output.LogInfo("Set %q as current workspace", r.Workspace.Name) + r.Output.LogInfo("Local workspace %s created (current)", r.Workspace.Name) return nil } diff --git a/pkg/cli/cmd/workspace/create/create_test.go b/pkg/cli/cmd/workspace/create/create_test.go index 7929acfbc8..02940cf4de 100644 --- a/pkg/cli/cmd/workspace/create/create_test.go +++ b/pkg/cli/cmd/workspace/create/create_test.go @@ -161,6 +161,12 @@ func Test_Run(t *testing.T) { err := runner.Run(context.Background()) require.NoError(t, err) + require.Equal(t, []any{ + output.LogOutput{ + Format: "Local workspace %s created (current)", + Params: []any{"defaultWorkspace"}, + }, + }, outputSink.Writes) }) } diff --git a/pkg/cli/cmd/workspace/delete/delete.go b/pkg/cli/cmd/workspace/delete/delete.go index 29c83b9eb3..4c8f6a2a33 100644 --- a/pkg/cli/cmd/workspace/delete/delete.go +++ b/pkg/cli/cmd/workspace/delete/delete.go @@ -131,5 +131,6 @@ func (r *Runner) Run(ctx context.Context) error { return err } + r.Output.LogInfo("Local workspace %s deleted", r.Workspace.Name) return nil } diff --git a/pkg/cli/cmd/workspace/delete/delete_test.go b/pkg/cli/cmd/workspace/delete/delete_test.go index 37b9051774..deb23ae7ad 100644 --- a/pkg/cli/cmd/workspace/delete/delete_test.go +++ b/pkg/cli/cmd/workspace/delete/delete_test.go @@ -115,7 +115,12 @@ func Test_Run(t *testing.T) { err := runner.Run(context.Background()) require.NoError(t, err) - require.Empty(t, outputSink.Writes) + require.Equal(t, []any{ + output.LogOutput{ + Format: "Local workspace %s deleted", + Params: []any{"test-workspace"}, + }, + }, outputSink.Writes) }) t.Run("Delete workspace bypass confirmation", func(t *testing.T) { outputSink := &output.MockOutput{} @@ -147,7 +152,12 @@ func Test_Run(t *testing.T) { err := runner.Run(context.Background()) require.NoError(t, err) - require.Empty(t, outputSink.Writes) + require.Equal(t, []any{ + output.LogOutput{ + Format: "Local workspace %s deleted", + Params: []any{"test-workspace"}, + }, + }, outputSink.Writes) }) t.Run("Delete workspace not confirmed", func(t *testing.T) { outputSink := &output.MockOutput{}