From a440af9161bfc4364895a0b13031b1ef3b9574ce Mon Sep 17 00:00:00 2001 From: Tomer Heber Date: Wed, 10 Jul 2024 09:53:58 -0500 Subject: [PATCH] Feat: support new apply-all environment property --- client/environment.go | 3 + client/project_policy.go | 2 + env0/resource_environment.go | 24 ++++++++ env0/resource_environment_test.go | 39 ++++++++++--- env0/resource_project_policy.go | 6 ++ env0/resource_project_policy_test.go | 83 +++++++++++++++------------- tests/integration/011_policy/main.tf | 18 +++--- 7 files changed, 120 insertions(+), 55 deletions(-) diff --git a/client/environment.go b/client/environment.go index a619e4f7..5ab6659a 100644 --- a/client/environment.go +++ b/client/environment.go @@ -122,6 +122,7 @@ type Environment struct { LatestDeploymentLog DeploymentLog `json:"latestDeploymentLog"` TerragruntWorkingDirectory string `json:"terragruntWorkingDirectory,omitempty"` VcsCommandsAlias string `json:"vcsCommandsAlias"` + VcsPrCommentsEnabled bool `json:"vcsPrCommentsEnabled" tfschema:"-"` BlueprintId string `json:"blueprintId" tfschema:"-"` IsRemoteBackend *bool `json:"isRemoteBackend" tfschema:"-"` IsArchived *bool `json:"isArchived" tfschema:"-"` @@ -144,6 +145,7 @@ type EnvironmentCreate struct { TTL *TTL `json:"ttl,omitempty" tfschema:"-"` TerragruntWorkingDirectory string `json:"terragruntWorkingDirectory,omitempty"` VcsCommandsAlias string `json:"vcsCommandsAlias"` + VcsPrCommentsEnabled bool `json:"vcsPrCommentsEnabled"` IsRemoteBackend *bool `json:"isRemoteBackend,omitempty" tfschema:"-"` Type string `json:"type,omitempty"` DriftDetectionRequest *DriftDetectionRequest `json:"driftDetectionRequest,omitempty" tfschema:"-"` @@ -193,6 +195,7 @@ type EnvironmentUpdate struct { AutoDeployByCustomGlob string `json:"autoDeployByCustomGlob"` TerragruntWorkingDirectory string `json:"terragruntWorkingDirectory,omitempty"` VcsCommandsAlias string `json:"vcsCommandsAlias,omitempty"` + VcsPrCommentsEnabled bool `json:"vcsPrCommentsEnabled"` RequiresApproval *bool `json:"requiresApproval,omitempty" tfschema:"-"` ContinuousDeployment *bool `json:"continuousDeployment,omitempty" tfschema:"-"` PullRequestPlanDeployments *bool `json:"pullRequestPlanDeployments,omitempty" tfschema:"-"` diff --git a/client/project_policy.go b/client/project_policy.go index 59bd74f9..d88aa6c9 100644 --- a/client/project_policy.go +++ b/client/project_policy.go @@ -18,6 +18,7 @@ type Policy struct { ForceRemoteBackend bool `json:"forceRemoteBackend"` DriftDetectionCron string `json:"driftDetectionCron"` DriftDetectionEnabled bool `json:"driftDetectionEnabled"` + VcsPrCommentsEnabledDefault bool `json:"vcsPrCommentsEnabledDefault"` } type PolicyUpdatePayload struct { @@ -36,6 +37,7 @@ type PolicyUpdatePayload struct { ForceRemoteBackend bool `json:"forceRemoteBackend"` DriftDetectionCron string `json:"driftDetectionCron"` DriftDetectionEnabled bool `json:"driftDetectionEnabled"` + VcsPrCommentsEnabledDefault bool `json:"vcsPrCommentsEnabledDefault"` } // Policy retrieves a policy from the API diff --git a/env0/resource_environment.go b/env0/resource_environment.go index 1aa9debd..8a19ac18 100644 --- a/env0/resource_environment.go +++ b/env0/resource_environment.go @@ -266,6 +266,12 @@ func resourceEnvironment() *schema.Resource { Description: "set an alias for this environment in favor of running VCS commands using PR comments against it. Additional details: https://docs.env0.com/docs/plan-and-apply-from-pr-comments", Optional: true, }, + "vcs_pr_comments_enabled": { + Type: schema.TypeBool, + Description: "set to 'true' to enable running VCS PR commands using PR comments. This can be set to 'true' (enabled) without setting alias in 'vcs_commands_alias'.", + Optional: true, + Default: false, + }, "is_inactive": { Type: schema.TypeBool, Description: "If 'true', it marks the environment as inactive. It can be re-activated by setting it to 'false' or removing this field.", @@ -380,6 +386,12 @@ func setEnvironmentSchema(ctx context.Context, d *schema.ResourceData, environme return fmt.Errorf("schema resource data serialization failed: %v", err) } + if val := d.Get("vcs_pr_comments_enabled").(bool); !val && environment.VcsPrCommentsEnabled { + // VcsPrCommentsEnabled may have been "forced" to be 'true', ignore the drift. + } else { + d.Set("vcs_pr_comments_enabled", environment.VcsPrCommentsEnabled) + } + if !isTemplateless(d) { if environment.LatestDeploymentLog.BlueprintId != "" { d.Set("template_id", environment.LatestDeploymentLog.BlueprintId) @@ -882,6 +894,11 @@ func getCreatePayload(d *schema.ResourceData, apiClient client.ApiClientInterfac return client.EnvironmentCreate{}, diag.Errorf("schema resource data deserialization failed: %v", err) } + if val := d.Get("vcs_commands_alias").(string); len(val) > 0 { + // if alias is set - VcsPrCommentsEnabled must be true. + payload.VcsPrCommentsEnabled = true + } + //lint:ignore SA1019 reason: https://github.com/hashicorp/terraform-plugin-sdk/issues/817 if val, exists := d.GetOkExists("deploy_on_push"); exists { payload.ContinuousDeployment = boolPtr(val.(bool)) @@ -1002,6 +1019,11 @@ func getUpdatePayload(d *schema.ResourceData) (client.EnvironmentUpdate, diag.Di return client.EnvironmentUpdate{}, diag.Errorf("schema resource data deserialization failed: %v", err) } + if val := d.Get("vcs_commands_alias").(string); len(val) > 0 { + // if alias is set - VcsPrCommentsEnabled must be true. + payload.VcsPrCommentsEnabled = true + } + if d.HasChange("approve_plan_automatically") { payload.RequiresApproval = boolPtr(!d.Get("approve_plan_automatically").(bool)) } @@ -1366,6 +1388,8 @@ func resourceEnvironmentImporter(ctx context.Context, d *schema.ResourceData, me d.Set("force_destroy", false) d.Set("removal_strategy", "destroy") + d.Set("vcs_pr_comments_enabled", environment.VcsCommandsAlias != "" || environment.VcsPrCommentsEnabled) + if getErr != nil { return nil, errors.New(getErr[0].Summary) } else { diff --git a/env0/resource_environment_test.go b/env0/resource_environment_test.go index 21e131ef..bda85b05 100644 --- a/env0/resource_environment_test.go +++ b/env0/resource_environment_test.go @@ -41,6 +41,7 @@ func TestUnitEnvironmentResource(t *testing.T) { }, TerragruntWorkingDirectory: "/terragrunt/directory/", VcsCommandsAlias: "alias", + VcsPrCommentsEnabled: true, IsRemoteBackend: &isRemoteBackendFalse, K8sNamespace: "namespace", } @@ -58,6 +59,7 @@ func TestUnitEnvironmentResource(t *testing.T) { }, TerragruntWorkingDirectory: "/terragrunt/directory/", VcsCommandsAlias: "alias2", + VcsPrCommentsEnabled: true, IsRemoteBackend: &isRemoteBackendTrue, IsArchived: boolPtr(true), K8sNamespace: "namespace", @@ -171,8 +173,9 @@ func TestUnitEnvironmentResource(t *testing.T) { DeployRequest: &client.DeployRequest{ BlueprintId: templateId, }, - IsRemoteBackend: &isRemoteBackendFalse, - K8sNamespace: environment.K8sNamespace, + IsRemoteBackend: &isRemoteBackendFalse, + K8sNamespace: environment.K8sNamespace, + VcsPrCommentsEnabled: true, }).Times(1).Return(environment, nil) mock.EXPECT().EnvironmentUpdate(updatedEnvironment.Id, client.EnvironmentUpdate{ Name: updatedEnvironment.Name, @@ -181,6 +184,7 @@ func TestUnitEnvironmentResource(t *testing.T) { VcsCommandsAlias: updatedEnvironment.VcsCommandsAlias, IsRemoteBackend: &isRemoteBackendTrue, IsArchived: updatedEnvironment.IsArchived, + VcsPrCommentsEnabled: true, }).Times(1).Return(updatedEnvironment, nil) mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, updatedEnvironment.Id).Times(3).Return(client.ConfigurationChanges{}, nil) mock.EXPECT().ConfigurationSetsAssignments("ENVIRONMENT", updatedEnvironment.Id).Times(3).Return(nil, nil) @@ -609,7 +613,11 @@ func TestUnitEnvironmentResource(t *testing.T) { ImportState: true, ImportStateId: environment.Id, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"force_destroy"}, + ImportStateVerifyIgnore: []string{"force_destroy", "vcs_pr_comments_enabled"}, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(accessor, "vcs_pr_comments_enabled", "true"), + resource.TestCheckResourceAttr(accessor, "force_destroy", "false"), + ), }, }, } @@ -626,8 +634,9 @@ func TestUnitEnvironmentResource(t *testing.T) { DeployRequest: &client.DeployRequest{ BlueprintId: templateId, }, - IsRemoteBackend: &isRemoteBackendFalse, - K8sNamespace: environment.K8sNamespace, + IsRemoteBackend: &isRemoteBackendFalse, + K8sNamespace: environment.K8sNamespace, + VcsPrCommentsEnabled: true, }).Times(1).Return(environment, nil) mock.EXPECT().Environment(environment.Id).Times(3).Return(environment, nil) mock.EXPECT().EnvironmentDestroy(environment.Id).Times(1) @@ -696,13 +705,15 @@ func TestUnitEnvironmentResource(t *testing.T) { Enabled: true, Cron: driftDetectionCron, }, - K8sNamespace: environment.K8sNamespace, + K8sNamespace: environment.K8sNamespace, + VcsPrCommentsEnabled: true, }).Times(1).Return(environment, nil) mock.EXPECT().EnvironmentUpdate(updatedEnvironment.Id, client.EnvironmentUpdate{ Name: updatedEnvironment.Name, AutoDeployByCustomGlob: autoDeployByCustomGlobDefault, TerragruntWorkingDirectory: updatedEnvironment.TerragruntWorkingDirectory, VcsCommandsAlias: updatedEnvironment.VcsCommandsAlias, + VcsPrCommentsEnabled: true, IsRemoteBackend: &isRemoteBackendTrue, IsArchived: updatedEnvironment.IsArchived, }).Times(1).Return(updatedEnvironment, nil) @@ -778,13 +789,15 @@ func TestUnitEnvironmentResource(t *testing.T) { Enabled: true, Cron: driftDetectionCron, }, - K8sNamespace: environment.K8sNamespace, + K8sNamespace: environment.K8sNamespace, + VcsPrCommentsEnabled: true, }).Times(1).Return(environment, nil) mock.EXPECT().EnvironmentUpdate(updatedEnvironment.Id, client.EnvironmentUpdate{ Name: updatedEnvironment.Name, AutoDeployByCustomGlob: autoDeployByCustomGlobDefault, TerragruntWorkingDirectory: updatedEnvironment.TerragruntWorkingDirectory, VcsCommandsAlias: updatedEnvironment.VcsCommandsAlias, + VcsPrCommentsEnabled: true, IsRemoteBackend: &isRemoteBackendTrue, IsArchived: updatedEnvironment.IsArchived, }).Times(1).Return(updatedEnvironment, nil) @@ -1941,6 +1954,7 @@ func TestUnitEnvironmentResource(t *testing.T) { }, TerragruntWorkingDirectory: environment.TerragruntWorkingDirectory, VcsCommandsAlias: environment.VcsCommandsAlias, + VcsPrCommentsEnabled: true, IsRemoteBackend: &isRemoteBackendFalse, K8sNamespace: environment.K8sNamespace, }).Times(1).Return(client.Environment{}, errors.New("error")) @@ -1973,6 +1987,7 @@ func TestUnitEnvironmentResource(t *testing.T) { }, TerragruntWorkingDirectory: environment.TerragruntWorkingDirectory, VcsCommandsAlias: environment.VcsCommandsAlias, + VcsPrCommentsEnabled: true, IsRemoteBackend: &isRemoteBackendFalse, K8sNamespace: environment.K8sNamespace, }).Times(1).Return(environment, nil) @@ -1983,6 +1998,7 @@ func TestUnitEnvironmentResource(t *testing.T) { AutoDeployByCustomGlob: autoDeployByCustomGlobDefault, TerragruntWorkingDirectory: updatedEnvironment.TerragruntWorkingDirectory, VcsCommandsAlias: updatedEnvironment.VcsCommandsAlias, + VcsPrCommentsEnabled: true, IsRemoteBackend: &isRemoteBackendTrue, IsArchived: boolPtr(true), }).Times(1).Return(client.Environment{}, errors.New("error")) @@ -2061,6 +2077,7 @@ func TestUnitEnvironmentResource(t *testing.T) { }, TerragruntWorkingDirectory: environment.TerragruntWorkingDirectory, VcsCommandsAlias: environment.VcsCommandsAlias, + VcsPrCommentsEnabled: true, IsRemoteBackend: &isRemoteBackendFalse, K8sNamespace: environment.K8sNamespace, }).Times(1).Return(environment, nil) @@ -2116,6 +2133,7 @@ func TestUnitEnvironmentWithoutTemplateResource(t *testing.T) { WorkspaceName: "workspace-name", TerragruntWorkingDirectory: "/terragrunt/directory/", VcsCommandsAlias: "alias", + VcsPrCommentsEnabled: true, LatestDeploymentLog: client.DeploymentLog{ BlueprintId: "id-template-0", }, @@ -2182,6 +2200,7 @@ func TestUnitEnvironmentWithoutTemplateResource(t *testing.T) { PullRequestPlanDeployments: environment.PullRequestPlanDeployments, TerragruntWorkingDirectory: environment.TerragruntWorkingDirectory, VcsCommandsAlias: environment.VcsCommandsAlias, + VcsPrCommentsEnabled: true, } templateCreatePayload := client.TemplateCreatePayload{ @@ -2431,7 +2450,11 @@ func TestUnitEnvironmentWithoutTemplateResource(t *testing.T) { ImportState: true, ImportStateId: environment.Id, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"force_destroy"}, + ImportStateVerifyIgnore: []string{"force_destroy", "vcs_pr_comments_enabled"}, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(accessor, "vcs_pr_comments_enabled", "true"), + resource.TestCheckResourceAttr(accessor, "force_destroy", "false"), + ), }, }, } diff --git a/env0/resource_project_policy.go b/env0/resource_project_policy.go index cfd9f838..c1c0184b 100644 --- a/env0/resource_project_policy.go +++ b/env0/resource_project_policy.go @@ -111,6 +111,12 @@ func resourceProjectPolicy() *schema.Resource { Optional: true, ValidateDiagFunc: ValidateCronExpression, }, + "vcs_pr_comments_enabled_default": { + Type: schema.TypeBool, + Description: "if 'true' all environments created in this project via the UI will be created with an 'enabled' running VCS PR commands using PR comments. Note: in the provider when creating an environment this is ignored, and must be configured explicitly by setting 'vcs_pr_comments_enabled' to 'true' or setting an alias in 'vcs_commands_alias'.", + Optional: true, + Default: false, + }, }, } } diff --git a/env0/resource_project_policy_test.go b/env0/resource_project_policy_test.go index c15262c9..ca5d2e5d 100644 --- a/env0/resource_project_policy_test.go +++ b/env0/resource_project_policy_test.go @@ -16,20 +16,21 @@ func TestUnitProjectPolicyResource(t *testing.T) { accessor := resourceAccessor(resourceType, resourceName) policy := client.Policy{ - Id: "id0", - ProjectId: "project0", - NumberOfEnvironments: intPtr(1), - NumberOfEnvironmentsTotal: intPtr(1), - IncludeCostEstimation: true, - SkipApplyWhenPlanIsEmpty: true, - DisableDestroyEnvironments: true, - SkipRedundantDeployments: true, - UpdatedBy: "updater0", - MaxTtl: stringPtr("inherit"), - DefaultTtl: stringPtr("inherit"), - ForceRemoteBackend: true, - DriftDetectionCron: "0 4 * * *", - DriftDetectionEnabled: true, + Id: "id0", + ProjectId: "project0", + NumberOfEnvironments: intPtr(1), + NumberOfEnvironmentsTotal: intPtr(1), + IncludeCostEstimation: true, + SkipApplyWhenPlanIsEmpty: true, + DisableDestroyEnvironments: true, + SkipRedundantDeployments: true, + UpdatedBy: "updater0", + MaxTtl: stringPtr("inherit"), + DefaultTtl: stringPtr("inherit"), + ForceRemoteBackend: true, + DriftDetectionCron: "0 4 * * *", + DriftDetectionEnabled: true, + VcsPrCommentsEnabledDefault: true, } updatedPolicy := client.Policy{ @@ -58,18 +59,19 @@ func TestUnitProjectPolicyResource(t *testing.T) { Steps: []resource.TestStep{ { Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ - "project_id": policy.ProjectId, - "number_of_environments": *policy.NumberOfEnvironments, - "number_of_environments_total": *policy.NumberOfEnvironmentsTotal, - "requires_approval_default": policy.RequiresApprovalDefault, - "include_cost_estimation": policy.IncludeCostEstimation, - "skip_apply_when_plan_is_empty": policy.SkipApplyWhenPlanIsEmpty, - "disable_destroy_environments": policy.DisableDestroyEnvironments, - "skip_redundant_deployments": policy.SkipRedundantDeployments, - "run_pull_request_plan_default": policy.RunPullRequestPlanDefault, - "continuous_deployment_default": policy.ContinuousDeploymentDefault, - "force_remote_backend": policy.ForceRemoteBackend, - "drift_detection_cron": policy.DriftDetectionCron, + "project_id": policy.ProjectId, + "number_of_environments": *policy.NumberOfEnvironments, + "number_of_environments_total": *policy.NumberOfEnvironmentsTotal, + "requires_approval_default": policy.RequiresApprovalDefault, + "include_cost_estimation": policy.IncludeCostEstimation, + "skip_apply_when_plan_is_empty": policy.SkipApplyWhenPlanIsEmpty, + "disable_destroy_environments": policy.DisableDestroyEnvironments, + "skip_redundant_deployments": policy.SkipRedundantDeployments, + "run_pull_request_plan_default": policy.RunPullRequestPlanDefault, + "continuous_deployment_default": policy.ContinuousDeploymentDefault, + "force_remote_backend": policy.ForceRemoteBackend, + "drift_detection_cron": policy.DriftDetectionCron, + "vcs_pr_comments_enabled_default": policy.VcsPrCommentsEnabledDefault, }), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(accessor, "project_id", policy.ProjectId), @@ -86,6 +88,7 @@ func TestUnitProjectPolicyResource(t *testing.T) { resource.TestCheckResourceAttr(accessor, "default_ttl", "inherit"), resource.TestCheckResourceAttr(accessor, "force_remote_backend", strconv.FormatBool(policy.ForceRemoteBackend)), resource.TestCheckResourceAttr(accessor, "drift_detection_cron", policy.DriftDetectionCron), + resource.TestCheckResourceAttr(accessor, "vcs_pr_comments_enabled_default", strconv.FormatBool(policy.VcsPrCommentsEnabledDefault)), ), }, { @@ -112,6 +115,7 @@ func TestUnitProjectPolicyResource(t *testing.T) { resource.TestCheckResourceAttr(accessor, "default_ttl", *updatedPolicy.DefaultTtl), resource.TestCheckResourceAttr(accessor, "force_remote_backend", strconv.FormatBool(updatedPolicy.ForceRemoteBackend)), resource.TestCheckResourceAttr(accessor, "drift_detection_cron", updatedPolicy.DriftDetectionCron), + resource.TestCheckResourceAttr(accessor, "vcs_pr_comments_enabled_default", strconv.FormatBool(updatedPolicy.VcsPrCommentsEnabledDefault)), ), }, }, @@ -121,19 +125,20 @@ func TestUnitProjectPolicyResource(t *testing.T) { gomock.InOrder( // Create mock.EXPECT().PolicyUpdate(client.PolicyUpdatePayload{ - ProjectId: policy.ProjectId, - NumberOfEnvironments: *policy.NumberOfEnvironments, - NumberOfEnvironmentsTotal: *policy.NumberOfEnvironmentsTotal, - RequiresApprovalDefault: policy.RequiresApprovalDefault, - IncludeCostEstimation: policy.IncludeCostEstimation, - SkipApplyWhenPlanIsEmpty: policy.SkipApplyWhenPlanIsEmpty, - DisableDestroyEnvironments: policy.DisableDestroyEnvironments, - SkipRedundantDeployments: policy.SkipRedundantDeployments, - MaxTtl: "inherit", - DefaultTtl: "inherit", - ForceRemoteBackend: true, - DriftDetectionEnabled: true, - DriftDetectionCron: policy.DriftDetectionCron, + ProjectId: policy.ProjectId, + NumberOfEnvironments: *policy.NumberOfEnvironments, + NumberOfEnvironmentsTotal: *policy.NumberOfEnvironmentsTotal, + RequiresApprovalDefault: policy.RequiresApprovalDefault, + IncludeCostEstimation: policy.IncludeCostEstimation, + SkipApplyWhenPlanIsEmpty: policy.SkipApplyWhenPlanIsEmpty, + DisableDestroyEnvironments: policy.DisableDestroyEnvironments, + SkipRedundantDeployments: policy.SkipRedundantDeployments, + MaxTtl: "inherit", + DefaultTtl: "inherit", + ForceRemoteBackend: true, + DriftDetectionEnabled: true, + DriftDetectionCron: policy.DriftDetectionCron, + VcsPrCommentsEnabledDefault: policy.VcsPrCommentsEnabledDefault, }).Times(1).Return(policy, nil), mock.EXPECT().Policy(gomock.Any()).Times(2).Return(policy, nil), // 1 after create, 1 before update // Update diff --git a/tests/integration/011_policy/main.tf b/tests/integration/011_policy/main.tf index ea478b18..2c30bc3c 100644 --- a/tests/integration/011_policy/main.tf +++ b/tests/integration/011_policy/main.tf @@ -18,17 +18,19 @@ resource "env0_project_policy" "test_policy" { disable_destroy_environments = false skip_redundant_deployments = false drift_detection_cron = var.second_run ? "0 4 * * *" : "0 3 * * *" + } resource "env0_project_policy" "test_policy_2" { - project_id = env0_project.test_project.id - number_of_environments = 1 - number_of_environments_total = 1 - requires_approval_default = true - include_cost_estimation = true - skip_apply_when_plan_is_empty = true - disable_destroy_environments = true - skip_redundant_deployments = true + project_id = env0_project.test_project.id + number_of_environments = 1 + number_of_environments_total = 1 + requires_approval_default = true + include_cost_estimation = true + skip_apply_when_plan_is_empty = true + disable_destroy_environments = true + skip_redundant_deployments = true + vcs_pr_comments_enabled_default = true } resource "env0_project_policy" "test_policy_ttl" {