Skip to content

Commit

Permalink
Merge branch 'main' into chore-filter-out-scopes-#751
Browse files Browse the repository at this point in the history
  • Loading branch information
TomerHeber authored Dec 4, 2023
2 parents 4ed8613 + e9451dc commit 9473db2
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 5 deletions.
2 changes: 2 additions & 0 deletions client/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ type Environment struct {
BlueprintId string `json:"blueprintId" tfschema:"-"`
IsRemoteBackend *bool `json:"isRemoteBackend" tfschema:"-"`
IsArchived *bool `json:"isArchived" tfschema:"-"`
IsRemoteApplyEnabled bool `json:"isRemoteApplyEnabled"`
}

type EnvironmentCreate struct {
Expand Down Expand Up @@ -186,6 +187,7 @@ type EnvironmentUpdate struct {
AutoDeployOnPathChangesOnly *bool `json:"autoDeployOnPathChangesOnly,omitempty" tfschema:"-"`
IsRemoteBackend *bool `json:"isRemoteBackend,omitempty" tfschema:"-"`
IsArchived *bool `json:"isArchived,omitempty" tfschema:"-"`
IsRemoteApplyEnabled bool `json:"isRemoteApplyEnabled"`
}

type EnvironmentDeployResponse struct {
Expand Down
2 changes: 1 addition & 1 deletion docs/resources/custom_role.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ resource "env0_custom_role" "custom_role_example" {
### Required

- `name` (String) The name of the custom role
- `permissions` (List of String) A list of permissions assigned to the role. Allowed values: (allowed values: VIEW_ORGANIZATION, EDIT_ORGANIZATION_SETTINGS, CREATE_AND_EDIT_TEMPLATES, CREATE_AND_EDIT_MODULES, CREATE_PROJECT, VIEW_PROJECT, EDIT_PROJECT_SETTINGS, MANAGE_PROJECT_TEMPLATES, EDIT_ENVIRONMENT_SETTINGS, ARCHIVE_ENVIRONMENT, OVERRIDE_MAX_TTL, CREATE_CROSS_PROJECT_ENVIRONMENTS, OVERRIDE_MAX_ENVIRONMENT_PROJECT_LIMITS, RUN_PLAN, RUN_APPLY, ABORT_DEPLOYMENT, RUN_TASK, CREATE_CUSTOM_ROLES, VIEW_DASHBOARD, VIEW_MODULES, READ_STATE, WRITE_STATE, FORCE_UNLOCK_WORKSPACE, MANAGE_BILLING, VIEW_AUDIT_LOGS, MANAGE_ENVIRONMENT_LOCK, CREATE_VCS_ENVIRONMENT, CREATE_AND_EDIT_PROVIDERS, VIEW_PROVIDERS, VIEW_ENVIRONMENT, ASSIGN_ROLE_ON_ENVIRONMENT)
- `permissions` (List of String) A list of permissions assigned to the role. Allowed values: (allowed values: VIEW_ORGANIZATION, EDIT_ORGANIZATION_SETTINGS, CREATE_AND_EDIT_TEMPLATES, CREATE_AND_EDIT_MODULES, CREATE_PROJECT, VIEW_PROJECT, EDIT_PROJECT_SETTINGS, MANAGE_PROJECT_TEMPLATES, EDIT_ENVIRONMENT_SETTINGS, ARCHIVE_ENVIRONMENT, OVERRIDE_MAX_TTL, CREATE_CROSS_PROJECT_ENVIRONMENTS, OVERRIDE_MAX_ENVIRONMENT_PROJECT_LIMITS, RUN_PLAN, RUN_APPLY, ABORT_DEPLOYMENT, RUN_TASK, CREATE_CUSTOM_ROLES, VIEW_DASHBOARD, VIEW_MODULES, READ_STATE, WRITE_STATE, FORCE_UNLOCK_WORKSPACE, MANAGE_BILLING, VIEW_AUDIT_LOGS, MANAGE_ENVIRONMENT_LOCK, CREATE_VCS_ENVIRONMENT, CREATE_AND_EDIT_PROVIDERS, VIEW_PROVIDERS, VIEW_ENVIRONMENT, ASSIGN_ROLE_ON_ENVIRONMENT, EDIT_ALLOW_REMOTE_APPLY)

### Optional

Expand Down
1 change: 1 addition & 0 deletions docs/resources/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ If true must specify one of the following - 'github_installation_id' if using Gi
- `force_destroy` (Boolean) Destroy safeguard. Must be enabled before delete/destroy
- `id` (String) the environment's id
- `is_inactive` (Boolean) If 'true', it marks the environment as inactive. It can be re-activated by setting it to 'false' or removing this field.
- `is_remote_apply_enabled` (Boolean) enables remote apply when set to true (defaults to false). Can only be enabled when is_remote_backend and approve_plan_automatically are enabled. Can only enabled for an existing environment
- `is_remote_backend` (Boolean) should use remote backend
- `output` (String) the deployment log output. Returns a json string. It can be either a map of key-value, or an array of (in case of Terragrunt run-all) of moduleName and a map of key-value. Note: if the deployment is still in progress returns 'null'
- `revision` (String) the revision the environment is to be run against
Expand Down
1 change: 1 addition & 0 deletions env0/resource_custom_role.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func resourceCustomRole() *schema.Resource {
"VIEW_PROVIDERS",
"VIEW_ENVIRONMENT",
"ASSIGN_ROLE_ON_ENVIRONMENT",
"EDIT_ALLOW_REMOTE_APPLY",
}

allowedCustomRoleTypesStr := fmt.Sprintf("(allowed values: %s)", strings.Join(allowedCustomRoleTypes, ", "))
Expand Down
26 changes: 22 additions & 4 deletions env0/resource_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,12 @@ func resourceEnvironment() *schema.Resource {
Optional: true,
ValidateDiagFunc: ValidateCronExpression,
},
"is_remote_apply_enabled": {
Type: schema.TypeBool,
Description: "enables remote apply when set to true (defaults to false). Can only be enabled when is_remote_backend and approve_plan_automatically are enabled. Can only enabled for an existing environment",
Optional: true,
Default: false,
},
},

CustomizeDiff: customdiff.ForceNewIf("template_id", func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) bool {
Expand Down Expand Up @@ -519,6 +525,11 @@ func validateTemplateProjectAssignment(d *schema.ResourceData, apiClient client.
func resourceEnvironmentCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
apiClient := meta.(client.ApiClientInterface)

isRemoteApplyEnabled := d.Get("is_remote_apply_enabled").(bool)
if isRemoteApplyEnabled {
return diag.Errorf("is_remote_apply_enabled cannot be set when creating a new environment. Set this value after the environment is created")
}

environmentPayload, createEnvPayloadErr := getCreatePayload(d, apiClient)
if createEnvPayloadErr != nil {
return createEnvPayloadErr
Expand Down Expand Up @@ -642,7 +653,7 @@ func shouldDeploy(d *schema.ResourceData) bool {
}

func shouldUpdate(d *schema.ResourceData) bool {
return d.HasChanges("name", "approve_plan_automatically", "deploy_on_push", "run_plan_on_pull_requests", "auto_deploy_by_custom_glob", "auto_deploy_on_path_changes_only", "terragrunt_working_directory", "vcs_commands_alias", "is_remote_backend", "is_inactive")
return d.HasChanges("name", "approve_plan_automatically", "deploy_on_push", "run_plan_on_pull_requests", "auto_deploy_by_custom_glob", "auto_deploy_on_path_changes_only", "terragrunt_working_directory", "vcs_commands_alias", "is_remote_backend", "is_inactive", "is_remote_apply_enabled")
}

func shouldUpdateTTL(d *schema.ResourceData) bool {
Expand Down Expand Up @@ -798,7 +809,7 @@ func getCreatePayload(d *schema.ResourceData, apiClient client.ApiClientInterfac
payload.IsRemoteBackend = boolPtr(val.(bool))
}

if err := assertDeploymentTriggers(d); err != nil {
if err := assertEnvironment(d); err != nil {
return client.EnvironmentCreate{}, err
}

Expand Down Expand Up @@ -844,7 +855,7 @@ func getCreatePayload(d *schema.ResourceData, apiClient client.ApiClientInterfac
return payload, nil
}

func assertDeploymentTriggers(d *schema.ResourceData) diag.Diagnostics {
func assertEnvironment(d *schema.ResourceData) diag.Diagnostics {
continuousDeployment := d.Get("deploy_on_push").(bool)
pullRequestPlanDeployments := d.Get("run_plan_on_pull_requests").(bool)
autoDeployOnPathChangesOnly := d.Get("auto_deploy_on_path_changes_only").(bool)
Expand All @@ -859,6 +870,13 @@ func assertDeploymentTriggers(d *schema.ResourceData) diag.Diagnostics {
}
}

isRemoteApplyEnabled := d.Get("is_remote_apply_enabled").(bool)
isRemotedBackend := d.Get("is_remote_backend").(bool)
approvePlanAutomatically := d.Get("approve_plan_automatically").(bool)
if isRemoteApplyEnabled && (!isRemotedBackend || !approvePlanAutomatically) {
return diag.Errorf("cannot set is_remote_apply_enabled when approve_plan_automatically or is_remote_backend are disabled")
}

return nil
}

Expand Down Expand Up @@ -893,7 +911,7 @@ func getUpdatePayload(d *schema.ResourceData) (client.EnvironmentUpdate, diag.Di
payload.IsArchived = boolPtr(d.Get("is_inactive").(bool))
}

if err := assertDeploymentTriggers(d); err != nil {
if err := assertEnvironment(d); err != nil {
return client.EnvironmentUpdate{}, err
}

Expand Down
128 changes: 128 additions & 0 deletions env0/resource_environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,118 @@ func TestUnitEnvironmentResource(t *testing.T) {
})
})

t.Run("remote apply is enabled", func(t *testing.T) {
templateId := "template-id"

environment := client.Environment{
Id: uuid.New().String(),
Name: "name",
ProjectId: "project-id",
LatestDeploymentLog: client.DeploymentLog{
BlueprintId: templateId,
},
IsRemoteBackend: boolPtr(true),
RequiresApproval: boolPtr(false),
}

updatedEnvironment := client.Environment{
Id: environment.Id,
Name: "name",
ProjectId: "project-id",
LatestDeploymentLog: client.DeploymentLog{
BlueprintId: templateId,
},
IsRemoteApplyEnabled: true,
}

testCase := resource.TestCase{
Steps: []resource.TestStep{
{
Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{
"name": environment.Name,
"project_id": environment.ProjectId,
"template_id": templateId,
"is_remote_backend": *environment.IsRemoteBackend,
"approve_plan_automatically": !*environment.RequiresApproval,
"force_destroy": true,
}),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "id", environment.Id),
resource.TestCheckResourceAttr(accessor, "name", environment.Name),
resource.TestCheckResourceAttr(accessor, "project_id", environment.ProjectId),
resource.TestCheckResourceAttr(accessor, "template_id", templateId),
resource.TestCheckResourceAttr(accessor, "is_remote_backend", "true"),
resource.TestCheckResourceAttr(accessor, "approve_plan_automatically", "true"),
resource.TestCheckResourceAttr(accessor, "is_remote_apply_enabled", "false"),
),
},
{
Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{
"name": environment.Name,
"project_id": environment.ProjectId,
"template_id": templateId,
"is_remote_backend": *environment.IsRemoteBackend,
"approve_plan_automatically": !*environment.RequiresApproval,
"force_destroy": true,
"is_remote_apply_enabled": true,
}),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "id", environment.Id),
resource.TestCheckResourceAttr(accessor, "name", environment.Name),
resource.TestCheckResourceAttr(accessor, "project_id", environment.ProjectId),
resource.TestCheckResourceAttr(accessor, "template_id", templateId),
resource.TestCheckResourceAttr(accessor, "is_remote_backend", "true"),
resource.TestCheckResourceAttr(accessor, "approve_plan_automatically", "true"),
resource.TestCheckResourceAttr(accessor, "is_remote_apply_enabled", "true"),
),
},
{
Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{
"name": environment.Name,
"project_id": environment.ProjectId,
"template_id": templateId,
"is_remote_backend": !*environment.IsRemoteBackend,
"approve_plan_automatically": !*environment.RequiresApproval,
"force_destroy": true,
"is_remote_apply_enabled": true,
}),
ExpectError: regexp.MustCompile("cannot set is_remote_apply_enabled when approve_plan_automatically or is_remote_backend are disabled"),
},
},
}

runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) {
gomock.InOrder(
mock.EXPECT().Template(environment.LatestDeploymentLog.BlueprintId).Times(1).Return(template, nil),
mock.EXPECT().EnvironmentCreate(client.EnvironmentCreate{
Name: environment.Name,
ProjectId: environment.ProjectId,

DeployRequest: &client.DeployRequest{
BlueprintId: templateId,
},
IsRemoteBackend: environment.IsRemoteBackend,
RequiresApproval: environment.RequiresApproval,
}).Times(1).Return(environment, nil),
mock.EXPECT().Environment(environment.Id).Times(1).Return(environment, nil),
mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, environment.Id).Times(1).Return(client.ConfigurationChanges{}, nil),
mock.EXPECT().Environment(environment.Id).Times(1).Return(environment, nil),
mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, environment.Id).Times(1).Return(client.ConfigurationChanges{}, nil),
mock.EXPECT().EnvironmentUpdate(updatedEnvironment.Id, client.EnvironmentUpdate{
Name: updatedEnvironment.Name,
IsRemoteBackend: updatedEnvironment.IsRemoteBackend,
RequiresApproval: updatedEnvironment.RequiresApproval,
IsRemoteApplyEnabled: updatedEnvironment.IsRemoteApplyEnabled,
}).Times(1).Return(updatedEnvironment, nil),
mock.EXPECT().Environment(environment.Id).Times(1).Return(updatedEnvironment, nil),
mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, environment.Id).Times(1).Return(client.ConfigurationChanges{}, nil),
mock.EXPECT().Environment(environment.Id).Times(1).Return(updatedEnvironment, nil),
mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, environment.Id).Times(1).Return(client.ConfigurationChanges{}, nil),
mock.EXPECT().EnvironmentDestroy(environment.Id).Times(1),
)
})
})

t.Run("Import By Id", func(t *testing.T) {
testCase := resource.TestCase{
Steps: []resource.TestStep{
Expand Down Expand Up @@ -1391,6 +1503,22 @@ func TestUnitEnvironmentResource(t *testing.T) {
}

testValidationFailures := func() {
t.Run("create environment with is_remote_apply_enabled set to 'true'", func(t *testing.T) {
runUnitTest(t, resource.TestCase{
Steps: []resource.TestStep{
{
Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{
"name": environment.Name,
"project_id": environment.ProjectId,
"template_id": environment.LatestDeploymentLog.BlueprintId,
"is_remote_apply_enabled": true,
}),
ExpectError: regexp.MustCompile("is_remote_apply_enabled cannot be set when creating a new environment"),
},
},
}, func(mockFunc *client.MockApiClientInterface) {})
})

t.Run("Failure in validation while glob is enabled and pathChanges no", func(t *testing.T) {
autoDeployWithCustomGlobEnabled := resource.TestCase{
Steps: []resource.TestStep{
Expand Down

0 comments on commit 9473db2

Please sign in to comment.