Repository: radius-project/radius

Issue:
Implement flag groups validation and mutually exclusive flags validation
## Overview of feature request
Cobra doesn't allow for flag groups and exclusive flag validations unless execute is called on the command.
Duplicate cobra logic defined [here](https://github.com/spf13/cobra/blob/main/flag_groups.go#L122) and [here](https://github.com/spf13/cobra/blob/main/flag_groups.go#L145) in radius integration test framework 

[AB#6482](https://dev.azure.com/azure-octo/e61041b4-555f-47ae-95b2-4f8ab480ea57/_workitems/edit/6482)

Discussion:
@nicolejms: too old - closing

Solution patch:
diff --git a/cmd/rad/cmd/root.go b/cmd/rad/cmd/root.go
index 4b1b97ba1..7004a0204 100644
--- a/cmd/rad/cmd/root.go
+++ b/cmd/rad/cmd/root.go
@@ -26,6 +26,7 @@ import (
 	env_list "github.com/project-radius/radius/pkg/cli/cmd/env/list"
 	"github.com/project-radius/radius/pkg/cli/cmd/env/namespace"
 	env_show "github.com/project-radius/radius/pkg/cli/cmd/env/show"
+	env_update "github.com/project-radius/radius/pkg/cli/cmd/env/update"
 	group "github.com/project-radius/radius/pkg/cli/cmd/group"
 	"github.com/project-radius/radius/pkg/cli/cmd/radInit"
 	recipe_list "github.com/project-radius/radius/pkg/cli/cmd/recipe/list"
@@ -199,6 +200,9 @@ func initSubCommands() {
 	envShowCmd, _ := env_show.NewCommand(framework)
 	envCmd.AddCommand(envShowCmd)
 
+	envUpdateCmd, _ := env_update.NewCommand(framework)
+	envCmd.AddCommand(envUpdateCmd)
+
 	workspaceCreateCmd, _ := workspace_create.NewCommand(framework)
 	workspaceCmd.AddCommand(workspaceCreateCmd)
 
diff --git a/pkg/cli/clients/clients.go b/pkg/cli/clients/clients.go
index 206d129f1..fe9f2b18d 100644
--- a/pkg/cli/clients/clients.go
+++ b/pkg/cli/clients/clients.go
@@ -138,7 +138,7 @@ type ApplicationsManagementClient interface {
 	CreateApplicationIfNotFound(ctx context.Context, applicationName string, resource corerp.ApplicationResource) error
 
 	DeleteApplication(ctx context.Context, applicationName string) (bool, error)
-	CreateEnvironment(ctx context.Context, envName, location, namespace, envKind, resourceId string, recipeProperties map[string]*corerp.EnvironmentRecipeProperties, providers *corerp.Providers, useDevRecipes bool) (bool, error)
+	CreateEnvironment(ctx context.Context, envName string, location string, envProperties *corerp.EnvironmentProperties) (bool, error)
 
 	// ListEnvironmentsInResourceGroup lists all environments in the configured scope (assumes configured scope is a resource group)
 	ListEnvironmentsInResourceGroup(ctx context.Context) ([]corerp.EnvironmentResource, error)
diff --git a/pkg/cli/clients/mock_applicationsclient.go b/pkg/cli/clients/mock_applicationsclient.go
index 2794a406c..ff5e60ba9 100644
--- a/pkg/cli/clients/mock_applicationsclient.go
+++ b/pkg/cli/clients/mock_applicationsclient.go
@@ -52,18 +52,18 @@ func (mr *MockApplicationsManagementClientMockRecorder) CreateApplicationIfNotFo
 }
 
 // CreateEnvironment mocks base method.
-func (m *MockApplicationsManagementClient) CreateEnvironment(arg0 context.Context, arg1, arg2, arg3, arg4, arg5 string, arg6 map[string]*v20220315privatepreview.EnvironmentRecipeProperties, arg7 *v20220315privatepreview.Providers, arg8 bool) (bool, error) {
+func (m *MockApplicationsManagementClient) CreateEnvironment(arg0 context.Context, arg1, arg2 string, arg3 *v20220315privatepreview.EnvironmentProperties) (bool, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "CreateEnvironment", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
+	ret := m.ctrl.Call(m, "CreateEnvironment", arg0, arg1, arg2, arg3)
 	ret0, _ := ret[0].(bool)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // CreateEnvironment indicates an expected call of CreateEnvironment.
-func (mr *MockApplicationsManagementClientMockRecorder) CreateEnvironment(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 interface{}) *gomock.Call {
+func (mr *MockApplicationsManagementClientMockRecorder) CreateEnvironment(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateEnvironment", reflect.TypeOf((*MockApplicationsManagementClient)(nil).CreateEnvironment), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateEnvironment", reflect.TypeOf((*MockApplicationsManagementClient)(nil).CreateEnvironment), arg0, arg1, arg2, arg3)
 }
 
 // CreateOrUpdateApplication mocks base method.
diff --git a/pkg/cli/cmd/commonflags/flags.go b/pkg/cli/cmd/commonflags/flags.go
index d0f19871a..a22950255 100644
--- a/pkg/cli/cmd/commonflags/flags.go
+++ b/pkg/cli/cmd/commonflags/flags.go
@@ -13,6 +13,21 @@ import (
 	"github.com/spf13/cobra"
 )
 
+const (
+	// AzureSubscriptionIdFlag provides azure subscription Id.
+	AzureSubscriptionIdFlag = "azure-subscription-id"
+	// AzureResourceGroupFlag provides azure resource group.
+	AzureResourceGroupFlag = "azure-resource-group"
+	// AWSRegionFlag provides aws region.
+	AWSRegionFlag = "aws-region"
+	// AWSAccountIdFlag provides aws accound id.
+	AWSAccountIdFlag = "aws-account-id"
+	// ClearEnvAzureFlag tells the command to clear azure scope on the environment it is configured.
+	ClearEnvAzureFlag = "clear-azure"
+	// ClearEnvAWSFlag tells the command to clear aws scope on the environment it is configured.
+	ClearEnvAWSFlag = "clear-aws"
+)
+
 func AddOutputFlag(cmd *cobra.Command) {
 	description := fmt.Sprintf("output format (supported formats are %s)", strings.Join(output.SupportedFormats(), ", "))
 	cmd.Flags().StringP("output", "o", output.DefaultFormat, description)
@@ -49,3 +64,35 @@ func AddParameterFlag(cmd *cobra.Command) {
 func AddRecipeFlag(cmd *cobra.Command) {
 	cmd.Flags().String("name", "", "The recipe name")
 }
+
+func AddAzureScopeFlags(cmd *cobra.Command) {
+	AddAzureSubscriptionFlag(cmd)
+	AddAzureResourceGroupFlag(cmd)
+	cmd.MarkFlagsRequiredTogether(AzureSubscriptionIdFlag, AzureResourceGroupFlag)
+	cmd.MarkFlagsMutuallyExclusive(AzureSubscriptionIdFlag, ClearEnvAzureFlag)
+	cmd.MarkFlagsMutuallyExclusive(AzureResourceGroupFlag, ClearEnvAzureFlag)
+}
+
+func AddAzureSubscriptionFlag(cmd *cobra.Command) {
+	cmd.Flags().String(AzureSubscriptionIdFlag, "", "The subscription ID where Azure resources will be deployed")
+}
+
+func AddAzureResourceGroupFlag(cmd *cobra.Command) {
+	cmd.Flags().String(AzureResourceGroupFlag, "", "The resource group where Azure resources will be deployed")
+}
+
+func AddAWSScopeFlags(cmd *cobra.Command) {
+	AddAWSRegionFlag(cmd)
+	AddAWSAccountFlag(cmd)
+	cmd.MarkFlagsRequiredTogether(AWSRegionFlag, AWSAccountIdFlag)
+	cmd.MarkFlagsMutuallyExclusive(AWSRegionFlag, ClearEnvAWSFlag)
+	cmd.MarkFlagsMutuallyExclusive(AWSAccountIdFlag, ClearEnvAWSFlag)
+}
+
+func AddAWSRegionFlag(cmd *cobra.Command) {
+	cmd.Flags().String(AWSRegionFlag, "", "The region where AWS resources will be deployed")
+}
+
+func AddAWSAccountFlag(cmd *cobra.Command) {
+	cmd.Flags().String(AWSAccountIdFlag, "", "The account ID where AWS resources will be deployed")
+}
diff --git a/pkg/cli/cmd/env/create/create.go b/pkg/cli/cmd/env/create/create.go
index cb8de9f97..a3bd2b58b 100644
--- a/pkg/cli/cmd/env/create/create.go
+++ b/pkg/cli/cmd/env/create/create.go
@@ -10,6 +10,7 @@ import (
 	"fmt"
 
 	corerp "github.com/project-radius/radius/pkg/corerp/api/v20220315privatepreview"
+	"github.com/project-radius/radius/pkg/to"
 	"github.com/spf13/cobra"
 
 	v1 "github.com/project-radius/radius/pkg/armrpc/api/v1"
@@ -158,7 +159,14 @@ func (r *Runner) Run(ctx context.Context) error {
 		return err
 	}
 
-	isEnvCreated, err := client.CreateEnvironment(ctx, r.EnvironmentName, v1.LocationGlobal, r.Namespace, "Kubernetes", "", map[string]*corerp.EnvironmentRecipeProperties{}, &corerp.Providers{}, !r.SkipDevRecipes)
+	envProperties := &corerp.EnvironmentProperties{
+		UseDevRecipes: to.Ptr(!r.SkipDevRecipes),
+		Compute: &corerp.KubernetesCompute{
+			Namespace: to.Ptr(r.Namespace),
+		},
+	}
+
+	isEnvCreated, err := client.CreateEnvironment(ctx, r.EnvironmentName, v1.LocationGlobal, envProperties)
 	if err != nil || !isEnvCreated {
 		return err
 	}
diff --git a/pkg/cli/cmd/env/update/update.go b/pkg/cli/cmd/env/update/update.go
new file mode 100644
index 000000000..babcfbc47
--- /dev/null
+++ b/pkg/cli/cmd/env/update/update.go
@@ -0,0 +1,244 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+// ------------------------------------------------------------
+
+package update
+
+import (
+	"context"
+	"fmt"
+
+	v1 "github.com/project-radius/radius/pkg/armrpc/api/v1"
+	"github.com/project-radius/radius/pkg/cli"
+	"github.com/project-radius/radius/pkg/cli/clients"
+	"github.com/project-radius/radius/pkg/cli/cmd/commonflags"
+	"github.com/project-radius/radius/pkg/cli/connections"
+	"github.com/project-radius/radius/pkg/cli/framework"
+	"github.com/project-radius/radius/pkg/cli/objectformats"
+	"github.com/project-radius/radius/pkg/cli/output"
+	"github.com/project-radius/radius/pkg/cli/workspaces"
+	corerp "github.com/project-radius/radius/pkg/corerp/api/v20220315privatepreview"
+	"github.com/project-radius/radius/pkg/to"
+	"github.com/spf13/cobra"
+)
+
+const (
+	envNotFoundErrMessage = "Environment does not exist. Please select a new environment and try again."
+	azureScopeTemplate    = "/subscriptions/%s/resourceGroups/%s"
+	awsScopeTemplate      = "/planes/aws/aws/accounts/%s/regions/%s"
+)
+
+// NewCommand creates an instance of the command and runner for the `rad env update` command.
+func NewCommand(factory framework.Factory) (*cobra.Command, framework.Runner) {
+	runner := NewRunner(factory)
+	runner.providers = &corerp.Providers{
+		Azure: &corerp.ProvidersAzure{},
+		Aws:   &corerp.ProvidersAws{},
+	}
+
+	cmd := &cobra.Command{
+		Use:   "update [environment]",
+		Short: "Update environment configuration",
+		Long: `Update environment configuration
+	
+This command updates the configuration of an environment for properties that are able to be changed.
+		
+Properties that can be updated include:
+- providers (Azure, AWS)
+		  
+All other properties require the environment to be deleted and recreated.
+`,
+		Args: cobra.ExactArgs(1),
+		Example: `
+## Add Azure cloud provider for deploying Azure resources
+rad env update myenv --azure-subscription-id **** --azure-resource-group myrg
+
+## Add AWS cloud provider for deploying AWS resources
+rad env update myenv --aws-region us-west-2 --aws-account-id *****
+
+## Remove Azure cloud provider
+rad env update myenv --clear-azure
+
+## Remove AWS cloud provider
+rad env update myenv --clear-aws
+`,
+		RunE: framework.RunCommand(runner),
+	}
+
+	commonflags.AddWorkspaceFlag(cmd)
+	commonflags.AddResourceGroupFlag(cmd)
+	cmd.Flags().Bool(commonflags.ClearEnvAzureFlag, false, "Specify if azure provider needs to be cleared on env")
+	cmd.Flags().Bool(commonflags.ClearEnvAWSFlag, false, "Specify if aws provider needs to be cleared on env")
+	commonflags.AddAzureScopeFlags(cmd)
+	commonflags.AddAWSScopeFlags(cmd)
+	commonflags.AddOutputFlag(cmd)
+	//TODO: https://github.com/project-radius/radius/issues/5247
+	commonflags.AddEnvironmentNameFlag(cmd)
+
+	return cmd, runner
+}
+
+// Runner is the runner implementation for the `rad env update` command.
+type Runner struct {
+	ConfigHolder      *framework.ConfigHolder
+	ConnectionFactory connections.Factory
+	Workspace         *workspaces.Workspace
+	Output            output.Interface
+
+	EnvName       string
+	clearEnvAzure bool
+	clearEnvAws   bool
+	providers     *corerp.Providers
+	noFlagsSet    bool
+}
+
+// NewRunner creates a new instance of the `rad env update` runner.
+func NewRunner(factory framework.Factory) *Runner {
+	return &Runner{
+		ConnectionFactory: factory.GetConnectionFactory(),
+		ConfigHolder:      factory.GetConfigHolder(),
+		Output:            factory.GetOutput(),
+	}
+}
+
+// Validate runs validation for the `rad env update` command.
+func (r *Runner) Validate(cmd *cobra.Command, args []string) error {
+	if cmd.Flags().NFlag() == 0 {
+		r.noFlagsSet = true
+		return cmd.Help()
+	}
+	workspace, err := cli.RequireWorkspace(cmd, r.ConfigHolder.Config, r.ConfigHolder.DirectoryConfig)
+	if err != nil {
+		return err
+	}
+	r.Workspace = workspace
+
+	r.Workspace.Scope, err = cli.RequireScope(cmd, *r.Workspace)
+	if err != nil {
+		return err
+	}
+
+	r.EnvName, err = cli.RequireEnvironmentNameArgs(cmd, args, *workspace)
+	if err != nil {
+		return err
+	}
+
+	// TODO: Validate Azure scope components (https://github.com/project-radius/radius/issues/5155)
+	azureSubId, err := cmd.Flags().GetString(commonflags.AzureSubscriptionIdFlag)
+	if err != nil {
+		return err
+	}
+
+	azureRgId, err := cmd.Flags().GetString(commonflags.AzureResourceGroupFlag)
+	if err != nil {
+		return err
+	}
+	if azureSubId != "" && azureRgId != "" {
+		r.providers.Azure.Scope = to.Ptr(fmt.Sprintf(azureScopeTemplate, azureSubId, azureRgId))
+	}
+
+	r.clearEnvAzure, err = cmd.Flags().GetBool(commonflags.ClearEnvAzureFlag)
+	if err != nil {
+		return err
+	}
+
+	// TODO: Validate AWS scope components (https://github.com/project-radius/radius/issues/5155)
+	// stsclient can be used to validate
+	awsRegion, err := cmd.Flags().GetString(commonflags.AWSRegionFlag)
+	if err != nil {
+		return err
+	}
+
+	awsAccountId, err := cmd.Flags().GetString(commonflags.AWSAccountIdFlag)
+	if err != nil {
+		return err
+	}
+	if awsRegion != "" && awsAccountId != "" {
+		r.providers.Aws.Scope = to.Ptr(fmt.Sprintf(awsScopeTemplate, awsAccountId, awsRegion))
+	}
+
+	r.clearEnvAws, err = cmd.Flags().GetBool(commonflags.ClearEnvAWSFlag)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Run runs the `rad env update` command.
+func (r *Runner) Run(ctx context.Context) error {
+	if r.noFlagsSet {
+		return nil
+	}
+	client, err := r.ConnectionFactory.CreateApplicationsManagementClient(ctx, *r.Workspace)
+	if err != nil {
+		return err
+	}
+
+	env, err := client.GetEnvDetails(ctx, r.EnvName)
+	if err != nil {
+		if clients.Is404Error(err) {
+			return &cli.FriendlyError{Message: envNotFoundErrMessage}
+		}
+		return err
+	}
+	// only update azure provider info if user requires it.
+	if r.clearEnvAzure && env.Properties.Providers != nil {
+		env.Properties.Providers.Azure = nil
+	} else if r.providers.Azure != nil && r.providers.Azure.Scope != nil {
+		if env.Properties.Providers == nil {
+			env.Properties.Providers = &corerp.Providers{}
+		}
+		env.Properties.Providers.Azure = r.providers.Azure
+	}
+	// only update aws provider info if user requires it.
+	if r.clearEnvAws && env.Properties.Providers != nil {
+		env.Properties.Providers.Aws = nil
+	} else if r.providers.Aws != nil && r.providers.Aws.Scope != nil {
+		if env.Properties.Providers == nil {
+			env.Properties.Providers = &corerp.Providers{}
+		}
+		env.Properties.Providers.Aws = r.providers.Aws
+	}
+
+	r.Output.LogInfo("Updating Environment...")
+
+	isEnvUpdated, err := client.CreateEnvironment(ctx, r.EnvName, v1.LocationGlobal, env.Properties)
+	if err != nil || !isEnvUpdated {
+		return &cli.FriendlyError{Message: fmt.Sprintf("failed to configure cloud provider scope to the environment %s: %s", r.EnvName, err.Error())}
+	}
+
+	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 := objectformats.OutputEnvObject{
+		EnvName:     *env.Name,
+		ComputeKind: computeKind,
+		Recipes:     recipeCount,
+		Providers:   providerCount,
+	}
+
+	err = r.Output.WriteFormatted("table", obj, objectformats.GetUpdateEnvironmentTableFormat())
+	if err != nil {
+		return err
+	}
+
+	r.Output.LogInfo("Successfully updated environment %q.", r.EnvName)
+
+	return nil
+}
diff --git a/pkg/cli/cmd/radInit/init.go b/pkg/cli/cmd/radInit/init.go
index af2c216a4..dfe9309c7 100644
--- a/pkg/cli/cmd/radInit/init.go
+++ b/pkg/cli/cmd/radInit/init.go
@@ -415,8 +415,16 @@ func (r *Runner) Run(ctx context.Context) error {
 			return err
 		}
 
+		envProperties := corerp.EnvironmentProperties{
+			Compute: &corerp.KubernetesCompute{
+				Namespace: to.Ptr(r.Namespace),
+			},
+			Providers: &providers,
+			UseDevRecipes: to.Ptr(!r.SkipDevRecipes),
+		}
+
 		r.Output.LogInfo("Configuring Cloud providers")
-		isEnvCreated, err := client.CreateEnvironment(ctx, r.EnvName, v1.LocationGlobal, r.Namespace, "kubernetes", "", map[string]*corerp.EnvironmentRecipeProperties{}, &providers, !r.SkipDevRecipes)
+		isEnvCreated, err := client.CreateEnvironment(ctx, r.EnvName, v1.LocationGlobal, &envProperties)
 		if err != nil || !isEnvCreated {
 			return &cli.FriendlyError{Message: "Failed to create radius environment"}
 		}
diff --git a/pkg/cli/cmd/recipe/register/register.go b/pkg/cli/cmd/recipe/register/register.go
index 17a14132b..492785c1c 100644
--- a/pkg/cli/cmd/recipe/register/register.go
+++ b/pkg/cli/cmd/recipe/register/register.go
@@ -13,7 +13,6 @@ import (
 	v1 "github.com/project-radius/radius/pkg/armrpc/api/v1"
 	"github.com/project-radius/radius/pkg/cli"
 	"github.com/project-radius/radius/pkg/cli/bicep"
-	"github.com/project-radius/radius/pkg/cli/cmd"
 	"github.com/project-radius/radius/pkg/cli/cmd/commonflags"
 	"github.com/project-radius/radius/pkg/cli/connections"
 	"github.com/project-radius/radius/pkg/cli/framework"
@@ -149,23 +148,23 @@ func (r *Runner) Run(ctx context.Context) error {
 	}
 
 	recipeProperties := envResource.Properties.Recipes
-	if recipeProperties[r.RecipeName] != nil {
-		return &cli.FriendlyError{Message: fmt.Sprintf("recipe with name %q already exists in the environment %q", r.RecipeName, r.Workspace.Environment)}
-	}
-
 	if recipeProperties == nil {
 		recipeProperties = map[string]*corerpapps.EnvironmentRecipeProperties{}
 	}
 
+	if recipeProperties[r.RecipeName] != nil {
+		return &cli.FriendlyError{Message: fmt.Sprintf("recipe with name %q already exists in the environment %q", r.RecipeName, r.Workspace.Environment)}
+	}
+
 	recipeProperties[r.RecipeName] = &corerpapps.EnvironmentRecipeProperties{
 		LinkType:     &r.LinkType,
 		TemplatePath: &r.TemplatePath,
 		Parameters:   bicep.ConvertToMapStringInterface(r.Parameters),
 	}
 
-	namespace := cmd.GetNamespace(envResource)
+	envResource.Properties.Recipes = recipeProperties
 
-	isEnvCreated, err := client.CreateEnvironment(ctx, r.Workspace.Environment, v1.LocationGlobal, namespace, "Kubernetes", *envResource.ID, recipeProperties, envResource.Properties.Providers, *envResource.Properties.UseDevRecipes)
+	isEnvCreated, err := client.CreateEnvironment(ctx, r.Workspace.Environment, v1.LocationGlobal, envResource.Properties)
 	if err != nil || !isEnvCreated {
 		return &cli.FriendlyError{Message: fmt.Sprintf("failed to register the recipe %s to the environment %s: %s", r.RecipeName, *envResource.ID, err.Error())}
 	}
diff --git a/pkg/cli/cmd/recipe/unregister/unregister.go b/pkg/cli/cmd/recipe/unregister/unregister.go
index cf32ae9fd..67c760f50 100644
--- a/pkg/cli/cmd/recipe/unregister/unregister.go
+++ b/pkg/cli/cmd/recipe/unregister/unregister.go
@@ -101,9 +101,9 @@ func (r *Runner) Run(ctx context.Context) error {
 		return err
 	}
 
-	namespace := cmd.GetNamespace(envResource)
 	delete(recipeProperties, r.RecipeName)
-	isEnvCreated, err := client.CreateEnvironment(ctx, r.Workspace.Environment, v1.LocationGlobal, namespace, "Kubernetes", *envResource.ID, recipeProperties, envResource.Properties.Providers, *envResource.Properties.UseDevRecipes)
+	envResource.Properties.Recipes = recipeProperties
+	isEnvCreated, err := client.CreateEnvironment(ctx, r.Workspace.Environment, v1.LocationGlobal, envResource.Properties)
 	if err != nil || !isEnvCreated {
 		return &cli.FriendlyError{Message: fmt.Sprintf("failed to unregister the recipe %s from the environment %s: %s", r.RecipeName, *envResource.ID, err.Error())}
 	}
diff --git a/pkg/cli/objectformats/objectformats.go b/pkg/cli/objectformats/objectformats.go
index 2cc4cbbf2..51381252b 100644
--- a/pkg/cli/objectformats/objectformats.go
+++ b/pkg/cli/objectformats/objectformats.go
@@ -193,6 +193,37 @@ func GetEnvironmentRecipesTableFormat() output.FormatterOptions {
 	}
 }
 
+type OutputEnvObject struct {
+	EnvName     string
+	ComputeKind string
+	Recipes     int
+	Providers   int
+}
+
+// GetUpdateEnvironmentTableFormat returns the fields to output from env object after upation.
+func GetUpdateEnvironmentTableFormat() output.FormatterOptions {
+	return output.FormatterOptions{
+		Columns: []output.Column{
+			{
+				Heading:  "NAME",
+				JSONPath: "{ .EnvName }",
+			},
+			{
+				Heading:  "COMPUTE",
+				JSONPath: "{ .ComputeKind }",
+			},
+			{
+				Heading:  "RECIPES",
+				JSONPath: "{ .Recipes }",
+			},
+			{
+				Heading:  "PROVIDERS",
+				JSONPath: "{ .Providers }",
+			},
+		},
+	}
+}
+
 func GetRecipeParamsTableFormat() output.FormatterOptions {
 	return output.FormatterOptions{
 		Columns: []output.Column{
diff --git a/pkg/cli/ucp/ucp_management.go b/pkg/cli/ucp/ucp_management.go
index c36101a93..ddbc0a5d1 100644
--- a/pkg/cli/ucp/ucp_management.go
+++ b/pkg/cli/ucp/ucp_management.go
@@ -302,15 +302,13 @@ func (amc *ARMApplicationsManagementClient) CreateApplicationIfNotFound(ctx cont
 }
 
 // Creates a radius environment resource
-func (amc *ARMApplicationsManagementClient) CreateEnvironment(ctx context.Context, envName string, location string, namespace string, envKind string, resourceId string, recipeProperties map[string]*corerpv20220315.EnvironmentRecipeProperties, providers *corerpv20220315.Providers, useDevRecipes bool) (bool, error) {
+func (amc *ARMApplicationsManagementClient) CreateEnvironment(ctx context.Context, envName string, location string, envProperties *corerpv20220315.EnvironmentProperties) (bool, error) {
 	client, err := corerpv20220315.NewEnvironmentsClient(amc.RootScope, &aztoken.AnonymousCredential{}, amc.ClientOptions)
 	if err != nil {
 		return false, err
 	}
 
-	envCompute := corerpv20220315.KubernetesCompute{Kind: &envKind, Namespace: &namespace, ResourceID: &resourceId}
-	properties := corerpv20220315.EnvironmentProperties{Compute: &envCompute, Recipes: recipeProperties, Providers: providers, UseDevRecipes: &useDevRecipes}
-	_, err = client.CreateOrUpdate(ctx, envName, corerpv20220315.EnvironmentResource{Location: &location, Properties: &properties}, &corerpv20220315.EnvironmentsClientCreateOrUpdateOptions{})
+	_, err = client.CreateOrUpdate(ctx, envName, corerpv20220315.EnvironmentResource{Location: &location, Properties: envProperties}, &corerpv20220315.EnvironmentsClientCreateOrUpdateOptions{})
 	if err != nil {
 		return false, err
 	}



Test patch:
diff --git a/pkg/cli/cmd/env/create/create_test.go b/pkg/cli/cmd/env/create/create_test.go
index 736dee9b0..170df4ae7 100644
--- a/pkg/cli/cmd/env/create/create_test.go
+++ b/pkg/cli/cmd/env/create/create_test.go
@@ -20,6 +20,7 @@ import (
 	"github.com/project-radius/radius/pkg/cli/output"
 	"github.com/project-radius/radius/pkg/cli/workspaces"
 	corerp "github.com/project-radius/radius/pkg/corerp/api/v20220315privatepreview"
+	"github.com/project-radius/radius/pkg/to"
 	"github.com/project-radius/radius/pkg/ucp/api/v20220901privatepreview"
 	"github.com/project-radius/radius/test/radcli"
 	"github.com/stretchr/testify/require"
@@ -110,8 +111,14 @@ func Test_Run_Success(t *testing.T) {
 		appManagementClient := clients.NewMockApplicationsManagementClient(ctrl)
 
 		namespaceClient := namespace.NewMockInterface(ctrl)
+		testEnvProperties := &corerp.EnvironmentProperties{
+			UseDevRecipes: to.Ptr(true),
+			Compute: &corerp.KubernetesCompute{
+				Namespace: to.Ptr("default"),
+			},
+		}
 		appManagementClient.EXPECT().
-			CreateEnvironment(context.Background(), "default", v1.LocationGlobal, "default", "Kubernetes", gomock.Any(), gomock.Any(), &corerp.Providers{}, gomock.Any()).
+			CreateEnvironment(context.Background(), "default", v1.LocationGlobal, testEnvProperties).
 			Return(true, nil).Times(1)
 
 		configFileInterface := framework.NewMockConfigFileInterface(ctrl)
@@ -148,8 +155,14 @@ func Test_Run_SkipDevRecipes(t *testing.T) {
 			appManagementClient := clients.NewMockApplicationsManagementClient(ctrl)
 
 			namespaceClient := namespace.NewMockInterface(ctrl)
+			testEnvProperties := &corerp.EnvironmentProperties{
+				UseDevRecipes: to.Ptr(false),
+				Compute: &corerp.KubernetesCompute{
+					Namespace: to.Ptr("default"),
+				},
+			}
 			appManagementClient.EXPECT().
-				CreateEnvironment(context.Background(), "default", v1.LocationGlobal, "default", "Kubernetes", gomock.Any(), gomock.Any(), gomock.Any(), false).
+				CreateEnvironment(context.Background(), "default", v1.LocationGlobal, testEnvProperties).
 				Return(true, nil).Times(1)
 
 			configFileInterface := framework.NewMockConfigFileInterface(ctrl)
@@ -184,8 +197,14 @@ func Test_Run_SkipDevRecipes(t *testing.T) {
 			appManagementClient := clients.NewMockApplicationsManagementClient(ctrl)
 
 			namespaceClient := namespace.NewMockInterface(ctrl)
+			testEnvProperties := &corerp.EnvironmentProperties{
+				UseDevRecipes: to.Ptr(true),
+				Compute: &corerp.KubernetesCompute{
+					Namespace: to.Ptr("default"),
+				},
+			}
 			appManagementClient.EXPECT().
-				CreateEnvironment(context.Background(), "default", v1.LocationGlobal, "default", "Kubernetes", gomock.Any(), gomock.Any(), gomock.Any(), true).
+				CreateEnvironment(context.Background(), "default", v1.LocationGlobal, testEnvProperties).
 				Return(true, nil).Times(1)
 
 			configFileInterface := framework.NewMockConfigFileInterface(ctrl)
diff --git a/pkg/cli/cmd/env/update/update_test.go b/pkg/cli/cmd/env/update/update_test.go
new file mode 100644
index 000000000..d54528c7d
--- /dev/null
+++ b/pkg/cli/cmd/env/update/update_test.go
@@ -0,0 +1,240 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+// ------------------------------------------------------------
+
+package update
+
+import (
+	"context"
+	"testing"
+
+	"github.com/golang/mock/gomock"
+	v1 "github.com/project-radius/radius/pkg/armrpc/api/v1"
+	"github.com/project-radius/radius/pkg/cli/clients"
+	"github.com/project-radius/radius/pkg/cli/connections"
+	"github.com/project-radius/radius/pkg/cli/framework"
+	"github.com/project-radius/radius/pkg/cli/objectformats"
+	"github.com/project-radius/radius/pkg/cli/output"
+	"github.com/project-radius/radius/pkg/cli/workspaces"
+	corerp "github.com/project-radius/radius/pkg/corerp/api/v20220315privatepreview"
+	"github.com/project-radius/radius/pkg/to"
+	"github.com/project-radius/radius/test/radcli"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_CommandValidation(t *testing.T) {
+	radcli.SharedCommandValidation(t, NewCommand)
+}
+
+func Test_Validate(t *testing.T) {
+	configWithWorkspace := radcli.LoadConfigWithWorkspace(t)
+
+	testcases := []radcli.ValidateInput{
+		{
+			Name:          "Update Env Command without any flags",
+			Input:         []string{"default"},
+			ExpectedValid: true,
+			ConfigHolder: framework.ConfigHolder{
+				ConfigFilePath: "",
+				Config:         configWithWorkspace,
+			},
+		},
+		{
+			Name:          "Update Env Command without env arg",
+			Input:         []string{},
+			ExpectedValid: false,
+			ConfigHolder: framework.ConfigHolder{
+				ConfigFilePath: "",
+				Config:         configWithWorkspace,
+			},
+		},
+		{
+			Name:          "Update Env Command with single provider set",
+			Input:         []string{"default", "--azure-subscription-id", "testSubId", "--azure-resource-group", "testResourceGroup"},
+			ExpectedValid: true,
+			ConfigHolder: framework.ConfigHolder{
+				ConfigFilePath: "",
+				Config:         configWithWorkspace,
+			},
+		},
+		{
+			Name: "Update Env Command with both providers set",
+			Input: []string{"default", "--azure-subscription-id", "testSubId", "--azure-resource-group", "testResourceGroup",
+				"--aws-region", "us-west-2", "--aws-account-id", "testAWSAccount",
+			},
+			ExpectedValid: true,
+			ConfigHolder: framework.ConfigHolder{
+				ConfigFilePath: "",
+				Config:         configWithWorkspace,
+			},
+		},
+	}
+	radcli.SharedValidateValidation(t, NewCommand, testcases)
+}
+
+func Test_Update(t *testing.T) {
+	t.Run("Success: Update Environment With Providers", func(t *testing.T) {
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+
+		environment := corerp.EnvironmentResource{
+			Name: to.Ptr("test-env"),
+			Properties: &corerp.EnvironmentProperties{
+				UseDevRecipes: to.Ptr(false),
+			},
+		}
+
+		appManagementClient := clients.NewMockApplicationsManagementClient(ctrl)
+		appManagementClient.EXPECT().
+			GetEnvDetails(gomock.Any(), "test-env").
+			Return(environment, nil).
+			Times(1)
+
+		testProviders := &corerp.Providers{
+			Azure: &corerp.ProvidersAzure{
+				Scope: to.Ptr("/subscriptions/testSubId/resourceGroups/test-group"),
+			},
+			Aws: &corerp.ProvidersAws{
+				Scope: to.Ptr("/planes/aws/aws/accounts/testAwsAccount/regions/us-west-2"),
+			},
+		}
+
+		testEnvProperties := &corerp.EnvironmentProperties{
+			UseDevRecipes: to.Ptr(false),
+			Providers:     testProviders,
+		}
+		appManagementClient.EXPECT().
+			CreateEnvironment(gomock.Any(), "test-env", v1.LocationGlobal, testEnvProperties).
+			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},
+			Workspace:         workspace,
+			Output:            outputSink,
+			EnvName:           "test-env",
+			providers:         testProviders,
+		}
+
+		err := runner.Run(context.Background())
+		require.NoError(t, err)
+
+		environment.Properties.Providers = testProviders
+		obj := objectformats.OutputEnvObject{
+			EnvName:   "test-env",
+			Recipes:   0,
+			Providers: 2,
+		}
+
+		expected := []any{
+			output.LogOutput{
+				Format: "Updating Environment...",
+			},
+			output.FormattedOutput{
+				Format:  "table",
+				Obj:     obj,
+				Options: objectformats.GetUpdateEnvironmentTableFormat(),
+			},
+			output.LogOutput{
+				Format: "Successfully updated environment %q.",
+				Params: []any{"test-env"},
+			},
+		}
+
+		require.Equal(t, expected, outputSink.Writes)
+	})
+	t.Run("Success: Update Environment With Existing Providers", func(t *testing.T) {
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+
+		environment := corerp.EnvironmentResource{
+			Name: to.Ptr("test-env"),
+			Properties: &corerp.EnvironmentProperties{
+				Providers: &corerp.Providers{
+					Azure: &corerp.ProvidersAzure{
+						Scope: to.Ptr("/subscriptions/testSubId-1/resourceGroups/test-group-1"),
+					},
+				},
+				UseDevRecipes: to.Ptr(false),
+			},
+		}
+
+		appManagementClient := clients.NewMockApplicationsManagementClient(ctrl)
+		appManagementClient.EXPECT().
+			GetEnvDetails(gomock.Any(), "test-env").
+			Return(environment, nil).
+			Times(1)
+
+		testProviders := &corerp.Providers{
+			Azure: &corerp.ProvidersAzure{
+				Scope: to.Ptr("/subscriptions/testSubId/resourceGroups/test-group"),
+			},
+			Aws: &corerp.ProvidersAws{
+				Scope: to.Ptr("/planes/aws/aws/accounts/testAwsAccount/regions/us-west-2"),
+			},
+		}
+
+		testEnvProperties := &corerp.EnvironmentProperties{
+			UseDevRecipes: to.Ptr(false),
+			Providers:     testProviders,
+		}
+		appManagementClient.EXPECT().
+			CreateEnvironment(gomock.Any(), "test-env", v1.LocationGlobal, testEnvProperties).
+			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},
+			Workspace:         workspace,
+			Output:            outputSink,
+			EnvName:           "test-env",
+			providers:         testProviders,
+		}
+
+		err := runner.Run(context.Background())
+		require.NoError(t, err)
+
+		environment.Properties.Providers = testProviders
+		obj := objectformats.OutputEnvObject{
+			EnvName:   "test-env",
+			Recipes:   0,
+			Providers: 2,
+		}
+
+		expected := []any{
+			output.LogOutput{
+				Format: "Updating Environment...",
+			},
+			output.FormattedOutput{
+				Format:  "table",
+				Obj:     obj,
+				Options: objectformats.GetUpdateEnvironmentTableFormat(),
+			},
+			output.LogOutput{
+				Format: "Successfully updated environment %q.",
+				Params: []any{"test-env"},
+			},
+		}
+
+		require.Equal(t, expected, outputSink.Writes)
+	})
+}
diff --git a/pkg/cli/cmd/radInit/init_test.go b/pkg/cli/cmd/radInit/init_test.go
index dbf3f2aa9..c48e55559 100644
--- a/pkg/cli/cmd/radInit/init_test.go
+++ b/pkg/cli/cmd/radInit/init_test.go
@@ -457,8 +457,19 @@ func Test_Run_InstallAndCreateEnvironment_WithAzureProvider_WithRecipes(t *testi
 		CreateUCPGroup(context.Background(), "deployments", "local", "default", gomock.Any()).
 		Return(true, nil).Times(1)
 	skipRecipes := false
+	testEnvProperties := &corerp.EnvironmentProperties{
+		Compute: &corerp.KubernetesCompute{
+			Namespace: to.Ptr("defaultNamespace"),
+		},
+		UseDevRecipes: to.Ptr(!skipRecipes),
+		Providers: &corerp.Providers{
+			Azure: &corerp.ProvidersAzure{
+				Scope: to.Ptr("/subscriptions/test-subscription/resourceGroups/test-rg"),
+			},
+		},
+	}
 	appManagementClient.EXPECT().
-		CreateEnvironment(context.Background(), "default", v1.LocationGlobal, "defaultNamespace", "kubernetes", gomock.Any(), gomock.Any(), gomock.Any(), !skipRecipes).
+		CreateEnvironment(context.Background(), "default", v1.LocationGlobal, testEnvProperties).
 		Return(true, nil).Times(1)
 
 	credentialManagementClient := cli_credential.NewMockCredentialManagementClient(ctrl)
@@ -522,8 +533,19 @@ func Test_Run_InstallAndCreateEnvironment_WithAWSProvider(t *testing.T) {
 	appManagementClient.EXPECT().
 		CreateUCPGroup(context.Background(), "deployments", "local", "default", gomock.Any()).
 		Return(true, nil).Times(1)
+	testEnvProperties := &corerp.EnvironmentProperties{
+		Compute: &corerp.KubernetesCompute{
+			Namespace: to.Ptr("defaultNamespace"),
+		},
+		UseDevRecipes: to.Ptr(true),
+		Providers: &corerp.Providers{
+			Aws: &corerp.ProvidersAws{
+				Scope: to.Ptr("/planes/aws/aws/accounts/test-account-id/regions/us-west-2"),
+			},
+		},
+	}
 	appManagementClient.EXPECT().
-		CreateEnvironment(context.Background(), "default", v1.LocationGlobal, "defaultNamespace", "kubernetes", gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
+		CreateEnvironment(context.Background(), "default", v1.LocationGlobal, testEnvProperties).
 		Return(true, nil).Times(1)
 
 	credentialManagementClient := cli_credential.NewMockCredentialManagementClient(ctrl)
@@ -584,8 +606,15 @@ func Test_Run_InstallAndCreateEnvironment_WithoutAzureProvider_WithSkipRecipes(t
 		CreateUCPGroup(context.Background(), "deployments", "local", "default", gomock.Any()).
 		Return(true, nil).Times(1)
 	skipRecipes := true
+	testEnvProperties := &corerp.EnvironmentProperties{
+		Compute: &corerp.KubernetesCompute{
+			Namespace: to.Ptr("defaultNamespace"),
+		},
+		UseDevRecipes: to.Ptr(!skipRecipes),
+		Providers:     &corerp.Providers{},
+	}
 	appManagementClient.EXPECT().
-		CreateEnvironment(context.Background(), "default", v1.LocationGlobal, "defaultNamespace", "kubernetes", gomock.Any(), gomock.Any(), gomock.Any(), !skipRecipes).
+		CreateEnvironment(context.Background(), "default", v1.LocationGlobal, testEnvProperties).
 		Return(true, nil).Times(1)
 
 	configFileInterface.EXPECT().
diff --git a/pkg/cli/cmd/recipe/register/register_test.go b/pkg/cli/cmd/recipe/register/register_test.go
index 76f339319..643b8ca71 100644
--- a/pkg/cli/cmd/recipe/register/register_test.go
+++ b/pkg/cli/cmd/recipe/register/register_test.go
@@ -102,31 +102,35 @@ func Test_Run(t *testing.T) {
 	t.Run("Register recipe Success", func(t *testing.T) {
 		ctrl := gomock.NewController(t)
 
-		envResource := v20220315privatepreview.EnvironmentResource{
-			ID:       to.Ptr("/planes/radius/local/resourcegroups/kind-kind/providers/applications.core/environments/kind-kind"),
-			Name:     to.Ptr("kind-kind"),
-			Type:     to.Ptr("applications.core/environments"),
-			Location: to.Ptr(v1.LocationGlobal),
-			Properties: &v20220315privatepreview.EnvironmentProperties{
-				UseDevRecipes: to.Ptr(true),
-				Recipes: map[string]*v20220315privatepreview.EnvironmentRecipeProperties{
-					"cosmosDB": {
-						LinkType:     to.Ptr(linkrp.MongoDatabasesResourceType),
-						TemplatePath: to.Ptr("testpublicrecipe.azurecr.io/bicep/modules/mongodatabases:v1"),
-					},
-				},
-				Compute: &v20220315privatepreview.KubernetesCompute{
-					Namespace: to.Ptr("default"),
-				},
+		testRecipes := map[string]*v20220315privatepreview.EnvironmentRecipeProperties{
+			"cosmosDB": {
+				LinkType:     to.Ptr(linkrp.MongoDatabasesResourceType),
+				TemplatePath: to.Ptr("testpublicrecipe.azurecr.io/bicep/modules/mongodatabases:v1"),
 			},
 		}
 
+		testEnvProperties := &v20220315privatepreview.EnvironmentProperties{
+			UseDevRecipes: to.Ptr(true),
+			Recipes:       testRecipes,
+			Compute: &v20220315privatepreview.KubernetesCompute{
+				Namespace: to.Ptr("default"),
+			},
+		}
+
+		envResource := v20220315privatepreview.EnvironmentResource{
+			ID:         to.Ptr("/planes/radius/local/resourcegroups/kind-kind/providers/applications.core/environments/kind-kind"),
+			Name:       to.Ptr("kind-kind"),
+			Type:       to.Ptr("applications.core/environments"),
+			Location:   to.Ptr(v1.LocationGlobal),
+			Properties: testEnvProperties,
+		}
+
 		appManagementClient := clients.NewMockApplicationsManagementClient(ctrl)
 		appManagementClient.EXPECT().
 			GetEnvDetails(gomock.Any(), gomock.Any()).
 			Return(envResource, nil).Times(1)
 		appManagementClient.EXPECT().
-			CreateEnvironment(context.Background(), "kind-kind", v1.LocationGlobal, "default", "Kubernetes", gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
+			CreateEnvironment(context.Background(), "kind-kind", v1.LocationGlobal, testEnvProperties).
 			Return(true, nil).Times(1)
 
 		outputSink := &output.MockOutput{}
@@ -147,24 +151,26 @@ func Test_Run(t *testing.T) {
 	t.Run("Register recipe with parameters", func(t *testing.T) {
 		ctrl := gomock.NewController(t)
 
-		envResource := v20220315privatepreview.EnvironmentResource{
-			ID:       to.Ptr("/planes/radius/local/resourcegroups/kind-kind/providers/applications.core/environments/kind-kind"),
-			Name:     to.Ptr("kind-kind"),
-			Type:     to.Ptr("applications.core/environments"),
-			Location: to.Ptr(v1.LocationGlobal),
-			Properties: &v20220315privatepreview.EnvironmentProperties{
-				UseDevRecipes: to.Ptr(true),
-				Recipes: map[string]*v20220315privatepreview.EnvironmentRecipeProperties{
-					"cosmosDB": {
-						LinkType:     to.Ptr(linkrp.MongoDatabasesResourceType),
-						TemplatePath: to.Ptr("testpublicrecipe.azurecr.io/bicep/modules/mongodatabases:v1"),
-						Parameters:   map[string]any{"throughput": 400},
-					},
-				},
-				Compute: &v20220315privatepreview.KubernetesCompute{
-					Namespace: to.Ptr("default"),
+		testEnvProperties := &v20220315privatepreview.EnvironmentProperties{
+			UseDevRecipes: to.Ptr(true),
+			Recipes: map[string]*v20220315privatepreview.EnvironmentRecipeProperties{
+				"cosmosDB": {
+					LinkType:     to.Ptr(linkrp.MongoDatabasesResourceType),
+					TemplatePath: to.Ptr("testpublicrecipe.azurecr.io/bicep/modules/mongodatabases:v1"),
+					Parameters:   map[string]any{"throughput": 400},
 				},
 			},
+			Compute: &v20220315privatepreview.KubernetesCompute{
+				Namespace: to.Ptr("default"),
+			},
+		}
+
+		envResource := v20220315privatepreview.EnvironmentResource{
+			ID:         to.Ptr("/planes/radius/local/resourcegroups/kind-kind/providers/applications.core/environments/kind-kind"),
+			Name:       to.Ptr("kind-kind"),
+			Type:       to.Ptr("applications.core/environments"),
+			Location:   to.Ptr(v1.LocationGlobal),
+			Properties: testEnvProperties,
 		}
 
 		appManagementClient := clients.NewMockApplicationsManagementClient(ctrl)
@@ -172,7 +178,7 @@ func Test_Run(t *testing.T) {
 			GetEnvDetails(gomock.Any(), gomock.Any()).
 			Return(envResource, nil).Times(1)
 		appManagementClient.EXPECT().
-			CreateEnvironment(context.Background(), "kind-kind", v1.LocationGlobal, "default", "Kubernetes", gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
+			CreateEnvironment(context.Background(), "kind-kind", v1.LocationGlobal, testEnvProperties).
 			Return(true, nil).Times(1)
 
 		outputSink := &output.MockOutput{}
@@ -193,28 +199,29 @@ func Test_Run(t *testing.T) {
 
 	t.Run("Register recipe with no namespace", func(t *testing.T) {
 		ctrl := gomock.NewController(t)
-
-		envResource := v20220315privatepreview.EnvironmentResource{
-			ID:       to.Ptr("/planes/radius/local/resourcegroups/kind-kind/providers/applications.core/environments/kind-kind"),
-			Name:     to.Ptr("kind-kind"),
-			Type:     to.Ptr("applications.core/environments"),
-			Location: to.Ptr(v1.LocationGlobal),
-			Properties: &v20220315privatepreview.EnvironmentProperties{
-				UseDevRecipes: to.Ptr(true),
-				Recipes: map[string]*v20220315privatepreview.EnvironmentRecipeProperties{
-					"cosmosDB": {
-						LinkType:     to.Ptr(linkrp.MongoDatabasesResourceType),
-						TemplatePath: to.Ptr("testpublicrecipe.azurecr.io/bicep/modules/mongodatabases:v1"),
-					},
+		testEnvProperties := &v20220315privatepreview.EnvironmentProperties{
+			UseDevRecipes: to.Ptr(true),
+			Recipes: map[string]*v20220315privatepreview.EnvironmentRecipeProperties{
+				"cosmosDB": {
+					LinkType:     to.Ptr(linkrp.MongoDatabasesResourceType),
+					TemplatePath: to.Ptr("testpublicrecipe.azurecr.io/bicep/modules/mongodatabases:v1"),
 				},
 			},
 		}
+
+		envResource := v20220315privatepreview.EnvironmentResource{
+			ID:         to.Ptr("/planes/radius/local/resourcegroups/kind-kind/providers/applications.core/environments/kind-kind"),
+			Name:       to.Ptr("kind-kind"),
+			Type:       to.Ptr("applications.core/environments"),
+			Location:   to.Ptr(v1.LocationGlobal),
+			Properties: testEnvProperties,
+		}
 		appManagementClient := clients.NewMockApplicationsManagementClient(ctrl)
 		appManagementClient.EXPECT().
 			GetEnvDetails(gomock.Any(), gomock.Any()).
 			Return(envResource, nil).Times(1)
 		appManagementClient.EXPECT().
-			CreateEnvironment(context.Background(), "kind-kind", v1.LocationGlobal, "", "Kubernetes", gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
+			CreateEnvironment(context.Background(), "kind-kind", v1.LocationGlobal, testEnvProperties).
 			Return(true, nil).Times(1)
 
 		outputSink := &output.MockOutput{}
diff --git a/pkg/cli/cmd/recipe/unregister/unregister_test.go b/pkg/cli/cmd/recipe/unregister/unregister_test.go
index c35b65574..298ade8f5 100644
--- a/pkg/cli/cmd/recipe/unregister/unregister_test.go
+++ b/pkg/cli/cmd/recipe/unregister/unregister_test.go
@@ -76,23 +76,25 @@ func Test_Run(t *testing.T) {
 		t.Run("Success", func(t *testing.T) {
 			ctrl := gomock.NewController(t)
 
-			envResource := v20220315privatepreview.EnvironmentResource{
-				ID:       to.Ptr("/planes/radius/local/resourcegroups/kind-kind/providers/applications.core/environments/kind-kind"),
-				Name:     to.Ptr("kind-kind"),
-				Type:     to.Ptr("applications.core/environments"),
-				Location: to.Ptr(v1.LocationGlobal),
-				Properties: &v20220315privatepreview.EnvironmentProperties{
-					UseDevRecipes: to.Ptr(true),
-					Recipes: map[string]*v20220315privatepreview.EnvironmentRecipeProperties{
-						"cosmosDB": {
-							LinkType:     to.Ptr(linkrp.MongoDatabasesResourceType),
-							TemplatePath: to.Ptr("testpublicrecipe.azurecr.io/bicep/modules/mongodatabases:v1"),
-						},
-					},
-					Compute: &v20220315privatepreview.KubernetesCompute{
-						Namespace: to.Ptr("default"),
+			testEnvProperties := &v20220315privatepreview.EnvironmentProperties{
+				UseDevRecipes: to.Ptr(true),
+				Recipes: map[string]*v20220315privatepreview.EnvironmentRecipeProperties{
+					"cosmosDB": {
+						LinkType:     to.Ptr(linkrp.MongoDatabasesResourceType),
+						TemplatePath: to.Ptr("testpublicrecipe.azurecr.io/bicep/modules/mongodatabases:v1"),
 					},
 				},
+				Compute: &v20220315privatepreview.KubernetesCompute{
+					Namespace: to.Ptr("default"),
+				},
+			}
+
+			envResource := v20220315privatepreview.EnvironmentResource{
+				ID:         to.Ptr("/planes/radius/local/resourcegroups/kind-kind/providers/applications.core/environments/kind-kind"),
+				Name:       to.Ptr("kind-kind"),
+				Type:       to.Ptr("applications.core/environments"),
+				Location:   to.Ptr(v1.LocationGlobal),
+				Properties: testEnvProperties,
 			}
 
 			appManagementClient := clients.NewMockApplicationsManagementClient(ctrl)
@@ -100,7 +102,7 @@ func Test_Run(t *testing.T) {
 				GetEnvDetails(gomock.Any(), gomock.Any()).
 				Return(envResource, nil).Times(1)
 			appManagementClient.EXPECT().
-				CreateEnvironment(context.Background(), "kind-kind", v1.LocationGlobal, "default", "Kubernetes", gomock.Any(), map[string]*v20220315privatepreview.EnvironmentRecipeProperties{}, gomock.Any(), gomock.Any()).
+				CreateEnvironment(context.Background(), "kind-kind", v1.LocationGlobal, testEnvProperties).
 				Return(true, nil).Times(1)
 
 			outputSink := &output.MockOutput{}
@@ -118,28 +120,30 @@ func Test_Run(t *testing.T) {
 		t.Run("No Namespace", func(t *testing.T) {
 			ctrl := gomock.NewController(t)
 
-			envResource := v20220315privatepreview.EnvironmentResource{
-				ID:       to.Ptr("/planes/radius/local/resourcegroups/kind-kind/providers/applications.core/environments/kind-kind"),
-				Name:     to.Ptr("kind-kind"),
-				Type:     to.Ptr("applications.core/environments"),
-				Location: to.Ptr(v1.LocationGlobal),
-				Properties: &v20220315privatepreview.EnvironmentProperties{
-					UseDevRecipes: to.Ptr(true),
-					Recipes: map[string]*v20220315privatepreview.EnvironmentRecipeProperties{
-						"cosmosDB": {
-							LinkType:     to.Ptr(linkrp.MongoDatabasesResourceType),
-							TemplatePath: to.Ptr("testpublicrecipe.azurecr.io/bicep/modules/mongodatabases:v1"),
-						},
+			testEnvProperties := &v20220315privatepreview.EnvironmentProperties{
+				UseDevRecipes: to.Ptr(true),
+				Recipes: map[string]*v20220315privatepreview.EnvironmentRecipeProperties{
+					"cosmosDB": {
+						LinkType:     to.Ptr(linkrp.MongoDatabasesResourceType),
+						TemplatePath: to.Ptr("testpublicrecipe.azurecr.io/bicep/modules/mongodatabases:v1"),
 					},
 				},
 			}
 
+			envResource := v20220315privatepreview.EnvironmentResource{
+				ID:         to.Ptr("/planes/radius/local/resourcegroups/kind-kind/providers/applications.core/environments/kind-kind"),
+				Name:       to.Ptr("kind-kind"),
+				Type:       to.Ptr("applications.core/environments"),
+				Location:   to.Ptr(v1.LocationGlobal),
+				Properties: testEnvProperties,
+			}
+
 			appManagementClient := clients.NewMockApplicationsManagementClient(ctrl)
 			appManagementClient.EXPECT().
 				GetEnvDetails(gomock.Any(), gomock.Any()).
 				Return(envResource, nil).Times(1)
 			appManagementClient.EXPECT().
-				CreateEnvironment(context.Background(), "kind-kind", v1.LocationGlobal, "", "Kubernetes", gomock.Any(), map[string]*v20220315privatepreview.EnvironmentRecipeProperties{}, gomock.Any(), gomock.Any()).
+				CreateEnvironment(context.Background(), "kind-kind", v1.LocationGlobal, testEnvProperties).
 				Return(true, nil).Times(1)
 
 			outputSink := &output.MockOutput{}
diff --git a/pkg/cli/objectformats/objectformats_test.go b/pkg/cli/objectformats/objectformats_test.go
index c06bd279f..f45bab88b 100644
--- a/pkg/cli/objectformats/objectformats_test.go
+++ b/pkg/cli/objectformats/objectformats_test.go
@@ -15,7 +15,7 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-func Test_EnvTableFormat(t *testing.T) {
+func Test_GenericEnvTableFormat(t *testing.T) {
 	obj := v20220315privatepreview.EnvironmentResource{
 		Name: to.Ptr("test_env_resource"),
 	}
@@ -27,3 +27,19 @@ func Test_EnvTableFormat(t *testing.T) {
 	expected := "NAME\ntest_env_resource\n"
 	require.Equal(t, expected, buffer.String())
 }
+
+func Test_EnvTableFormat(t *testing.T) {
+	obj := OutputEnvObject{
+		EnvName:     "test_env_resource",
+		ComputeKind: "kubernetes",
+		Recipes:     3,
+		Providers:   2,
+	}
+
+	buffer := &bytes.Buffer{}
+	err := output.Write(output.FormatTable, obj, buffer, GetUpdateEnvironmentTableFormat())
+	require.NoError(t, err)
+
+	expected := "NAME               COMPUTE     RECIPES   PROVIDERS\ntest_env_resource  kubernetes  3         2\n"
+	require.Equal(t, expected, buffer.String())
+}