Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Add glob_pattern validation when cd/PrPlan enabled #190

Merged
merged 11 commits into from
Dec 21, 2021
63 changes: 47 additions & 16 deletions env0/resource_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,10 @@ func resourceEnvironment() *schema.Resource {
Default: true,
},
"auto_deploy_by_custom_glob": {
Type: schema.TypeString,
Description: "redeploy on file filter pattern",
RequiredWith: []string{"auto_deploy_on_path_changes_only"},
Optional: true,
Default: "",
Type: schema.TypeString,
Description: "redeploy on file filter pattern",
Optional: true,
Default: "",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be redundant (I think* that d.get always returns the false value of that type)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

},
"deployment_id": {
Type: schema.TypeString,
Expand Down Expand Up @@ -220,7 +219,11 @@ func setEnvironmentConfigurationSchema(d *schema.ResourceData, configurationVari
func resourceEnvironmentCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
apiClient := meta.(client.ApiClientInterface)

payload := getCreatePayload(d, apiClient)
payload, createEnvPayloadErr := getCreatePayload(d, apiClient)

if createEnvPayloadErr != nil {
return diag.Errorf("%v", createEnvPayloadErr)
}

environment, err := apiClient.EnvironmentCreate(payload)
if err != nil {
Expand Down Expand Up @@ -285,7 +288,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")
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")
}

func shouldUpdateTTL(d *schema.ResourceData) bool {
Expand All @@ -303,7 +306,12 @@ func deploy(d *schema.ResourceData, apiClient client.ApiClientInterface) diag.Di
}

func update(d *schema.ResourceData, apiClient client.ApiClientInterface) diag.Diagnostics {
payload := getUpdatePayload(d)
payload, updateEnvPayloadErr := getUpdatePayload(d)

if updateEnvPayloadErr != nil {
return diag.Errorf("%v", updateEnvPayloadErr)
}

_, err := apiClient.EnvironmentUpdate(d.Id(), payload)
if err != nil {
return diag.Errorf("could not update environment: %v", err)
Expand Down Expand Up @@ -337,7 +345,7 @@ func resourceEnvironmentDelete(ctx context.Context, d *schema.ResourceData, meta
return nil
}

func getCreatePayload(d *schema.ResourceData, apiClient client.ApiClientInterface) client.EnvironmentCreate {
func getCreatePayload(d *schema.ResourceData, apiClient client.ApiClientInterface) (client.EnvironmentCreate, diag.Diagnostics) {
payload := client.EnvironmentCreate{}

if name, ok := d.GetOk("name"); ok {
Expand All @@ -351,8 +359,8 @@ func getCreatePayload(d *schema.ResourceData, apiClient client.ApiClientInterfac
payload.WorkspaceName = workspace.(string)
}

continuousDeployment := d.Get("deploy_on_push").(bool)
if d.HasChange("deploy_on_push") {
continuousDeployment := d.Get("deploy_on_push").(bool)
payload.ContinuousDeployment = &continuousDeployment
}

Expand All @@ -361,15 +369,19 @@ func getCreatePayload(d *schema.ResourceData, apiClient client.ApiClientInterfac
payload.RequiresApproval = &requiresApproval
}

pullRequestPlanDeployments := d.Get("run_plan_on_pull_requests").(bool)
if d.HasChange("run_plan_on_pull_requests") {
pullRequestPlanDeployments := d.Get("run_plan_on_pull_requests").(bool)
payload.PullRequestPlanDeployments = &pullRequestPlanDeployments
}

autoDeployOnPathChangesOnly := d.Get("auto_deploy_on_path_changes_only").(bool)
payload.AutoDeployOnPathChangesOnly = &autoDeployOnPathChangesOnly

payload.AutoDeployByCustomGlob = d.Get("auto_deploy_by_custom_glob").(string)
err := assertDeploymentTriggers(payload.AutoDeployByCustomGlob, continuousDeployment, pullRequestPlanDeployments, autoDeployOnPathChangesOnly)
if err != nil {
return client.EnvironmentCreate{}, err
}

if configuration, ok := d.GetOk("configuration"); ok {
configurationChanges := getConfigurationVariables(configuration.([]interface{}))
Expand All @@ -384,10 +396,23 @@ func getCreatePayload(d *schema.ResourceData, apiClient client.ApiClientInterfac

payload.DeployRequest = &deployPayload

return payload
return payload, nil
}

func getUpdatePayload(d *schema.ResourceData) client.EnvironmentUpdate {
func assertDeploymentTriggers(autoDeployByCustomGlob string, continuousDeployment bool, pullRequestPlanDeployments bool, autoDeployOnPathChangesOnly bool) diag.Diagnostics {
if autoDeployByCustomGlob != "" {
if (continuousDeployment == false) &&
(pullRequestPlanDeployments == false) {
return diag.Errorf("run_plan_on_pull_requests or deploy_on_push must be enabled for auto_deploy_by_custom_glob")
}
if autoDeployOnPathChangesOnly == false {
return diag.Errorf("cannot set auto_deploy_by_custom_glob when auto_deploy_on_path_changes_only is disabled")
}
}
return nil
}
Comment on lines +401 to +412
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏻


func getUpdatePayload(d *schema.ResourceData) (client.EnvironmentUpdate, diag.Diagnostics) {
payload := client.EnvironmentUpdate{}

if name, ok := d.GetOk("name"); ok {
Expand All @@ -397,19 +422,25 @@ func getUpdatePayload(d *schema.ResourceData) client.EnvironmentUpdate {
requiresApproval := !d.Get("approve_plan_automatically").(bool)
payload.RequiresApproval = &requiresApproval
}

continuousDeployment := d.Get("deploy_on_push").(bool)
if d.HasChange("deploy_on_push") {
continuousDeployment := d.Get("deploy_on_push").(bool)
payload.ContinuousDeployment = &continuousDeployment
}
pullRequestPlanDeployments := d.Get("run_plan_on_pull_requests").(bool)
if d.HasChange("run_plan_on_pull_requests") {
pullRequestPlanDeployments := d.Get("run_plan_on_pull_requests").(bool)
payload.PullRequestPlanDeployments = &pullRequestPlanDeployments
}
autoDeployOnPathChangesOnly := d.Get("auto_deploy_on_path_changes_only").(bool)
payload.AutoDeployOnPathChangesOnly = &autoDeployOnPathChangesOnly
payload.AutoDeployByCustomGlob = d.Get("auto_deploy_by_custom_glob").(string)

return payload
err := assertDeploymentTriggers(payload.AutoDeployByCustomGlob, continuousDeployment, pullRequestPlanDeployments, autoDeployOnPathChangesOnly)
if err != nil {
return client.EnvironmentUpdate{}, err
}

return payload, nil
}

func getDeployPayload(d *schema.ResourceData, apiClient client.ApiClientInterface, isRedeploy bool) client.DeployRequest {
Expand Down
87 changes: 73 additions & 14 deletions env0/resource_environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,8 +532,8 @@ func TestUnitEnvironmentResource(t *testing.T) {
BlueprintId: "template-id",
},

ContinuousDeployment: &truthyFruity,
AutoDeployOnPathChangesOnly: &truthyFruity,
ContinuousDeployment: &truthyFruity,
RequiresApproval: &falsey,
PullRequestPlanDeployments: &truthyFruity,
AutoDeployByCustomGlob: ".*",
Expand All @@ -546,11 +546,9 @@ func TestUnitEnvironmentResource(t *testing.T) {
BlueprintId: environment.LatestDeploymentLog.BlueprintId,
},

ContinuousDeployment: &falsey,
AutoDeployOnPathChangesOnly: &autoDeployOnPathChangesOnlyDefault,
RequiresApproval: &truthyFruity,
PullRequestPlanDeployments: &falsey,
AutoDeployByCustomGlob: autoDeployByCustomGlobDefault,
ContinuousDeployment: &falsey,
RequiresApproval: &truthyFruity,
PullRequestPlanDeployments: &falsey,
}

testCase := resource.TestCase{
Expand All @@ -574,15 +572,16 @@ func TestUnitEnvironmentResource(t *testing.T) {
resource.TestCheckResourceAttr(accessor, "approve_plan_automatically", "true"),
resource.TestCheckResourceAttr(accessor, "run_plan_on_pull_requests", "true"),
resource.TestCheckResourceAttr(accessor, "auto_deploy_on_path_changes_only", "true"),
resource.TestCheckResourceAttr(accessor, "auto_deploy_by_custom_glob", ".*"),
resource.TestCheckResourceAttr(accessor, "auto_deploy_by_custom_glob", environment.AutoDeployByCustomGlob),
),
},
{
Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{
"name": environment.Name,
"project_id": environment.ProjectId,
"template_id": environment.LatestDeploymentLog.BlueprintId,
"force_destroy": true,
"name": environment.Name,
"project_id": environment.ProjectId,
"template_id": environment.LatestDeploymentLog.BlueprintId,
"force_destroy": true,
"auto_deploy_on_path_changes_only": false,
}),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "id", environment.Id),
Expand All @@ -591,8 +590,8 @@ func TestUnitEnvironmentResource(t *testing.T) {
resource.TestCheckResourceAttr(accessor, "template_id", environment.LatestDeploymentLog.BlueprintId),
resource.TestCheckResourceAttr(accessor, "approve_plan_automatically", "false"),
resource.TestCheckResourceAttr(accessor, "run_plan_on_pull_requests", "false"),
resource.TestCheckResourceAttr(accessor, "auto_deploy_on_path_changes_only", "true"),
resource.TestCheckResourceAttr(accessor, "auto_deploy_by_custom_glob", ""),
resource.TestCheckResourceAttr(accessor, "auto_deploy_on_path_changes_only", "false"),
resource.TestCheckResourceAttr(accessor, "auto_deploy_by_custom_glob", autoDeployByCustomGlobDefault),
),
},
},
Expand All @@ -604,7 +603,7 @@ func TestUnitEnvironmentResource(t *testing.T) {
Name: environment.Name,

ContinuousDeployment: &falsey,
AutoDeployOnPathChangesOnly: &autoDeployOnPathChangesOnlyDefault,
AutoDeployOnPathChangesOnly: &falsey,
RequiresApproval: &truthyFruity,
PullRequestPlanDeployments: &falsey,
AutoDeployByCustomGlob: autoDeployByCustomGlobDefault,
Expand Down Expand Up @@ -674,6 +673,66 @@ func TestUnitEnvironmentResource(t *testing.T) {
})
})

t.Run("Failure in validation while glob is enabled and pathChanges no", func(t *testing.T) {
autoDeployWithCustomGlobEnabled := resource.TestCase{
Steps: []resource.TestStep{
{
Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{
"name": environment.Name,
"project_id": environment.ProjectId,
"template_id": environment.LatestDeploymentLog.BlueprintId,
"auto_deploy_on_path_changes_only": false,
"force_destroy": true,
"run_plan_on_pull_requests": true,
"auto_deploy_by_custom_glob": "/**",
}),
ExpectError: regexp.MustCompile("cannot set auto_deploy_by_custom_glob when auto_deploy_on_path_changes_only is disabled"),
},
{
Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{
"name": environment.Name,
"project_id": environment.ProjectId,
"template_id": environment.LatestDeploymentLog.BlueprintId,
"auto_deploy_on_path_changes_only": true,
"run_plan_on_pull_requests": true,
"auto_deploy_by_custom_glob": "/**",
"force_destroy": true,
}),
ExpectNonEmptyPlan: true,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "id", environment.Id),
resource.TestCheckResourceAttr(accessor, "name", environment.Name),
resource.TestCheckResourceAttr(accessor, "project_id", environment.ProjectId),
),
},
},
}
runUnitTest(t, autoDeployWithCustomGlobEnabled, func(mock *client.MockApiClientInterface) {
mock.EXPECT().EnvironmentCreate(gomock.Any()).Times(1).Return(environment, nil)
mock.EXPECT().Environment(gomock.Any()).Times(1).Return(environment, nil)
mock.EXPECT().ConfigurationVariables(gomock.Any(), gomock.Any()).Times(1).Return(client.ConfigurationChanges{}, nil)
mock.EXPECT().EnvironmentDestroy(environment.Id).Times(1)
})
})

t.Run("Failure in validation while prPlan and CD are disabled", func(t *testing.T) {
autoDeployWithCustomGlobEnabled := resource.TestCase{
Steps: []resource.TestStep{
{
Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{
"name": environment.Name,
"project_id": environment.ProjectId,
"template_id": environment.LatestDeploymentLog.BlueprintId,
"force_destroy": true,
"auto_deploy_by_custom_glob": "/**",
}),
ExpectError: regexp.MustCompile("run_plan_on_pull_requests or deploy_on_push must be enabled.*"),
},
},
}
runUnitTest(t, autoDeployWithCustomGlobEnabled, func(mock *client.MockApiClientInterface) {})
})

t.Run("Failure in create", func(t *testing.T) {
testCase := resource.TestCase{
Steps: []resource.TestStep{
Expand Down
1 change: 1 addition & 0 deletions tests/integration/012_environment/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ resource "env0_environment" "example" {
name = "environment configuration variable"
value = "value"
}
approve_plan_automatically = true
}

data "env0_environment" "test" {
Expand Down