Skip to content

Commit

Permalink
support deployment of an application to a scope "scope1" using enviro…
Browse files Browse the repository at this point in the history
…nment in a different scope "scope2" (radius-project#7895)

# Description

Today, rad deploy can use only the environments that are in same scope
as where the application is being deployed to (either default scope or
one specified by -g).
It should be able to use environments in different groups to deploy
application.

## Type of change

- This pull request fixes a bug in Radius and has an approved issue
(issue link required).
Fixes: radius-project#7520

---------

Signed-off-by: nithyatsu <nithyasu@microsoft.com>
  • Loading branch information
nithyatsu authored Sep 10, 2024
1 parent 3d2825f commit 897f2aa
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 79 deletions.
26 changes: 25 additions & 1 deletion pkg/cli/clivalidation.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,34 @@ func RequireEnvironmentName(cmd *cobra.Command, args []string, workspace workspa
return environmentName, err
}

// RequireEnvironmentNameOrID checks if an environment name or ID is provided as a flag or as a default environment in the workspace,
// and returns an error if neither is present. It also handles any errors that occur while parsing the resource.
func RequireEnvironmentNameOrID(cmd *cobra.Command, args []string, workspace workspaces.Workspace) (string, error) {
environmentNameOrID, err := cmd.Flags().GetString("environment")
if err != nil {
return "", err
}

// We store the environment id in config
if environmentNameOrID == "" && workspace.Environment != "" {
environmentNameOrID = workspace.Environment
}

if environmentNameOrID == "" && workspace.IsEditableWorkspace() {
// Setting a default environment only applies to editable workspaces
return "", fmt.Errorf("no environment name or ID provided and no default environment set, " +
"either pass in an environment name or ID or set a default environment by using `rad env switch`")
} else if environmentNameOrID == "" {
return "", fmt.Errorf("no environment name or ID provided, pass in an environment name or ID")
}

return environmentNameOrID, err
}

// DidSpecifyEnvironmentName checks if an environment name is provided as a flag
func DidSpecifyEnvironmentName(cmd *cobra.Command, args []string) bool {
environmentName, err := cmd.Flags().GetString("environment")
return err == nil && environmentName != ""
return err == nil && environmentName != "" && !strings.HasPrefix(environmentName, resources.SegmentSeparator)
}

// RequireKubeContext is used by commands that need a kubernetes context name to be specified using -c flag or has a default kubecontext
Expand Down
36 changes: 21 additions & 15 deletions pkg/cli/cmd/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ rad deploy myapp.bicep --environment production
# deploy using a specific environment and resource group
rad deploy myapp.bicep --environment production --group mygroup
# deploy using an environment ID and a resource group. The application will be deployed in mygroup scope, using the specified environment.
# use this option if the environment is in a different group.
rad deploy myapp.bicep --environment /planes/radius/local/resourcegroups/prod/providers/Applications.Core/environments/prod --group mygroup
# specify a string parameter
rad deploy myapp.bicep --parameters version=latest
Expand Down Expand Up @@ -123,12 +127,12 @@ type Runner struct {
Deploy deploy.Interface
Output output.Interface

ApplicationName string
EnvironmentName string
FilePath string
Parameters map[string]map[string]any
Workspace *workspaces.Workspace
Providers *clients.Providers
ApplicationName string
EnvironmentNameOrID string
FilePath string
Parameters map[string]map[string]any
Workspace *workspaces.Workspace
Providers *clients.Providers
}

// NewRunner creates a new instance of the `rad deploy` runner.
Expand Down Expand Up @@ -166,7 +170,7 @@ func (r *Runner) Validate(cmd *cobra.Command, args []string) error {
// does not exist.
workspace.Scope = scope

r.EnvironmentName, err = cli.RequireEnvironmentName(cmd, args, *workspace)
r.EnvironmentNameOrID, err = cli.RequireEnvironmentNameOrID(cmd, args, *workspace)
if err != nil {
return err
}
Expand All @@ -183,17 +187,17 @@ func (r *Runner) Validate(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
env, err := client.GetEnvironment(cmd.Context(), r.EnvironmentName)
env, err := client.GetEnvironment(cmd.Context(), r.EnvironmentNameOrID)
if err != nil {
// If the error is not a 404, return it
if !clients.Is404Error(err) {
return err
}

// If the environment doesn't exist, but the user specified it as
// If the environment doesn't exist, but the user specified its name or resource id as
// a command-line option, return an error
if cli.DidSpecifyEnvironmentName(cmd, args) {
return clierrors.Message("The environment %q does not exist in scope %q. Run `rad env create` first.", r.EnvironmentName, r.Workspace.Scope)
return clierrors.Message("The environment %q does not exist in scope %q. Run `rad env create` first. You could also provide the environment ID if the environment exists in a different group.", r.EnvironmentNameOrID, r.Workspace.Scope)
}

// If we got here, it means that the error was a 404 and the user did not specify the environment name.
Expand All @@ -202,8 +206,10 @@ func (r *Runner) Validate(cmd *cobra.Command, args []string) error {

r.Providers = &clients.Providers{}
r.Providers.Radius = &clients.RadiusProvider{}
r.Providers.Radius.EnvironmentID = r.Workspace.Scope + "/providers/applications.core/environments/" + r.EnvironmentName
r.Workspace.Environment = r.Providers.Radius.EnvironmentID
if env.ID != nil {
r.Providers.Radius.EnvironmentID = *env.ID
r.Workspace.Environment = r.Providers.Radius.EnvironmentID
}

if r.ApplicationName != "" {
r.Providers.Radius.ApplicationID = r.Workspace.Scope + "/providers/applications.core/applications/" + r.ApplicationName
Expand Down Expand Up @@ -272,7 +278,7 @@ func (r *Runner) Run(ctx context.Context) error {
}

// Validate that the environment exists already
_, err = client.GetEnvironment(ctx, r.EnvironmentName)
_, err = client.GetEnvironment(ctx, r.EnvironmentNameOrID)
if err != nil {
// If the error is not a 404, return it
if !clients.Is404Error(err) {
Expand All @@ -298,11 +304,11 @@ func (r *Runner) Run(ctx context.Context) error {
if r.ApplicationName == "" {
progressText = fmt.Sprintf(
"Deploying template '%v' into environment '%v' from workspace '%v'...\n\n"+
"Deployment In Progress...", r.FilePath, r.EnvironmentName, r.Workspace.Name)
"Deployment In Progress...", r.FilePath, r.EnvironmentNameOrID, r.Workspace.Name)
} else {
progressText = fmt.Sprintf(
"Deploying template '%v' for application '%v' and environment '%v' from workspace '%v'...\n\n"+
"Deployment In Progress... ", r.FilePath, r.ApplicationName, r.EnvironmentName, r.Workspace.Name)
"Deployment In Progress... ", r.FilePath, r.ApplicationName, r.EnvironmentNameOrID, r.Workspace.Name)
}

_, err = r.Deploy.DeployWithProgress(ctx, deploy.Options{
Expand Down
126 changes: 77 additions & 49 deletions pkg/cli/cmd/deploy/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func Test_CommandValidation(t *testing.T) {
func Test_Validate(t *testing.T) {
configWithWorkspace := radcli.LoadConfigWithWorkspace(t)
testcases := []radcli.ValidateInput{

{
Name: "rad deploy - valid",
Input: []string{"app.bicep"},
Expand All @@ -53,7 +54,7 @@ func Test_Validate(t *testing.T) {
},
ConfigureMocks: func(mocks radcli.ValidateMocks) {
mocks.ApplicationManagementClient.EXPECT().
GetEnvironment(gomock.Any(), radcli.TestEnvironmentName).
GetEnvironment(gomock.Any(), "/planes/radius/local/resourceGroups/test-resource-group/providers/Applications.Core/environments/test-environment").
Return(v20231001preview.EnvironmentResource{}, nil).
Times(1)
},
Expand All @@ -68,7 +69,7 @@ func Test_Validate(t *testing.T) {
},
ConfigureMocks: func(mocks radcli.ValidateMocks) {
mocks.ApplicationManagementClient.EXPECT().
GetEnvironment(gomock.Any(), radcli.TestEnvironmentName).
GetEnvironment(gomock.Any(), radcli.TestEnvironmentID).
Return(v20231001preview.EnvironmentResource{}, nil).
Times(1)

Expand Down Expand Up @@ -114,6 +115,30 @@ func Test_Validate(t *testing.T) {

},
},
{
Name: "rad deploy - valid with env ID",
Input: []string{"app.bicep", "-e", "/planes/radius/local/resourceGroups/test-resource-group/providers/applications.core/environments/prod"},
ExpectedValid: true,
ConfigHolder: framework.ConfigHolder{
ConfigFilePath: "",
Config: configWithWorkspace,
},
ConfigureMocks: func(mocks radcli.ValidateMocks) {
mocks.ApplicationManagementClient.EXPECT().
GetEnvironment(gomock.Any(), "/planes/radius/local/resourceGroups/test-resource-group/providers/applications.core/environments/prod").
Return(v20231001preview.EnvironmentResource{
ID: to.Ptr("/planes/radius/local/resourceGroups/test-resource-group/providers/applications.core/environments/prod"),
}, nil).
Times(1)
},
ValidateCallback: func(t *testing.T, obj framework.Runner) {
runner := obj.(*Runner)
scope := "/planes/radius/local/resourceGroups/test-resource-group"
environmentID := scope + "/providers/applications.core/environments/prod"
require.Equal(t, scope, runner.Workspace.Scope)
require.Equal(t, environmentID, runner.Workspace.Environment)
},
},
{
Name: "rad deploy - valid with app and env",
Input: []string{"app.bicep", "-e", "prod", "-a", "my-app"},
Expand All @@ -125,7 +150,9 @@ func Test_Validate(t *testing.T) {
ConfigureMocks: func(mocks radcli.ValidateMocks) {
mocks.ApplicationManagementClient.EXPECT().
GetEnvironment(gomock.Any(), "prod").
Return(v20231001preview.EnvironmentResource{}, nil).
Return(v20231001preview.EnvironmentResource{
ID: to.Ptr("/planes/radius/local/resourceGroups/test-resource-group/providers/applications.core/environments/prod"),
}, nil).
Times(1)
},
ValidateCallback: func(t *testing.T, obj framework.Runner) {
Expand Down Expand Up @@ -243,7 +270,7 @@ func Test_Run(t *testing.T) {
filePath := "app.bicep"
progressText := fmt.Sprintf(
"Deploying template '%v' into environment '%v' from workspace '%v'...\n\n"+
"Deployment In Progress...", filePath, radcli.TestEnvironmentName, workspace.Name)
"Deployment In Progress...", filePath, radcli.TestEnvironmentID, workspace.Name)

options := deploy.Options{
Workspace: *workspace,
Expand All @@ -266,14 +293,14 @@ func Test_Run(t *testing.T) {

outputSink := &output.MockOutput{}
runner := &Runner{
Bicep: bicep,
Deploy: deployMock,
Output: outputSink,
FilePath: filePath,
EnvironmentName: radcli.TestEnvironmentName,
Parameters: map[string]map[string]any{},
Workspace: workspace,
Providers: provider,
Bicep: bicep,
Deploy: deployMock,
Output: outputSink,
FilePath: filePath,
EnvironmentNameOrID: radcli.TestEnvironmentID,
Parameters: map[string]map[string]any{},
Workspace: workspace,
Providers: provider,
}

err := runner.Run(context.Background())
Expand Down Expand Up @@ -317,7 +344,7 @@ func Test_Run(t *testing.T) {
filePath := "app.bicep"
progressText := fmt.Sprintf(
"Deploying template '%v' into environment '%v' from workspace '%v'...\n\n"+
"Deployment In Progress...", filePath, radcli.TestEnvironmentName, workspace.Name)
"Deployment In Progress...", filePath, radcli.TestEnvironmentID, workspace.Name)

options := deploy.Options{
Workspace: *workspace,
Expand All @@ -340,14 +367,14 @@ func Test_Run(t *testing.T) {

outputSink := &output.MockOutput{}
runner := &Runner{
Bicep: bicep,
Deploy: deployMock,
Output: outputSink,
Providers: &ProviderConfig,
FilePath: filePath,
EnvironmentName: radcli.TestEnvironmentName,
Parameters: map[string]map[string]any{},
Workspace: workspace,
Bicep: bicep,
Deploy: deployMock,
Output: outputSink,
Providers: &ProviderConfig,
FilePath: filePath,
EnvironmentNameOrID: radcli.TestEnvironmentID,
Parameters: map[string]map[string]any{},
Workspace: workspace,
}

err := runner.Run(context.Background())
Expand Down Expand Up @@ -410,16 +437,16 @@ func Test_Run(t *testing.T) {
}

runner := &Runner{
Bicep: bicep,
ConnectionFactory: &connections.MockFactory{ApplicationsManagementClient: appManagmentMock},
Deploy: deployMock,
Output: outputSink,
Providers: &providers,
FilePath: "app.bicep",
ApplicationName: "test-application",
EnvironmentName: radcli.TestEnvironmentName,
Parameters: map[string]map[string]any{},
Workspace: workspace,
Bicep: bicep,
ConnectionFactory: &connections.MockFactory{ApplicationsManagementClient: appManagmentMock},
Deploy: deployMock,
Output: outputSink,
Providers: &providers,
FilePath: "app.bicep",
ApplicationName: "test-application",
EnvironmentNameOrID: radcli.TestEnvironmentName,
Parameters: map[string]map[string]any{},
Workspace: workspace,
}

err := runner.Run(context.Background())
Expand Down Expand Up @@ -477,16 +504,16 @@ func Test_Run(t *testing.T) {
}

runner := &Runner{
Bicep: bicep,
ConnectionFactory: &connections.MockFactory{ApplicationsManagementClient: appManagmentMock},
Deploy: deployMock,
Output: outputSink,
Providers: &providers,
FilePath: "app.bicep",
ApplicationName: "appdoesntexist",
EnvironmentName: "envdoesntexist",
Parameters: map[string]map[string]any{},
Workspace: workspace,
Bicep: bicep,
ConnectionFactory: &connections.MockFactory{ApplicationsManagementClient: appManagmentMock},
Deploy: deployMock,
Output: outputSink,
Providers: &providers,
FilePath: "app.bicep",
ApplicationName: "appdoesntexist",
EnvironmentNameOrID: "envdoesntexist",
Parameters: map[string]map[string]any{},
Workspace: workspace,
}

err := runner.Run(context.Background())
Expand All @@ -500,6 +527,7 @@ func Test_Run(t *testing.T) {
})

t.Run("Deployment with missing parameters", func(t *testing.T) {
//t.Skip()
ctrl := gomock.NewController(t)
defer ctrl.Finish()

Expand Down Expand Up @@ -532,14 +560,14 @@ func Test_Run(t *testing.T) {
}

runner := &Runner{
Bicep: bicep,
ConnectionFactory: &connections.MockFactory{},
Output: outputSink,
Providers: &providers,
EnvironmentName: radcli.TestEnvironmentName,
FilePath: "app.bicep",
Parameters: map[string]map[string]any{},
Workspace: workspace,
Bicep: bicep,
ConnectionFactory: &connections.MockFactory{},
Output: outputSink,
Providers: &providers,
EnvironmentNameOrID: radcli.TestEnvironmentName,
FilePath: "app.bicep",
Parameters: map[string]map[string]any{},
Workspace: workspace,
}

err := runner.Run(context.Background())
Expand Down
Loading

0 comments on commit 897f2aa

Please sign in to comment.