diff --git a/octopusdeploy/schema_action_apply_terraform_template.go b/octopusdeploy/schema_action_apply_terraform_template.go index 16bade85b..7c76099c9 100644 --- a/octopusdeploy/schema_action_apply_terraform_template.go +++ b/octopusdeploy/schema_action_apply_terraform_template.go @@ -42,6 +42,15 @@ func addTerraformTemplateAdvancedOptionsSchema(element *schema.Resource) { } } +func addTerraformTemplatePlanSchema(element *schema.Resource) { + element.Schema["is_plan"] = &schema.Schema{ + Optional: true, + Type: schema.TypeBool, + Default: false, + Description: "Determines whether the action only generates a terraform plan", + } +} + func addTerraformTemplateAwsAccountSchema(element *schema.Resource) { element.Schema["aws_account"] = &schema.Schema{ Elem: &schema.Resource{ @@ -166,10 +175,7 @@ func flattenTerraformTemplate(properties map[string]octopusdeploy.PropertyValue) return []interface{}{flattenedMap} } -func expandApplyTerraformTemplateAction(flattenedAction map[string]interface{}) *octopusdeploy.DeploymentAction { - action := expandAction(flattenedAction) - action.ActionType = "Octopus.TerraformApply" - +func expandTerraformTemplateAction(flattenedAction map[string]interface{}, action *octopusdeploy.DeploymentAction) { if v, ok := flattenedAction["template"]; ok { template := v.(*schema.Set).List()[0].(map[string]interface{}) @@ -275,7 +281,15 @@ func expandApplyTerraformTemplateAction(flattenedAction map[string]interface{}) action.Properties["Octopus.Action.AzureAccount.Variable"] = octopusdeploy.NewPropertyValue(v.(string), false) } } - +} +func expandApplyTerraformTemplateAction(flattenedAction map[string]interface{}) *octopusdeploy.DeploymentAction { + action := expandAction(flattenedAction) + if isPlan, ok := flattenedAction["is_plan"].(bool); ok && isPlan { + action.ActionType = "Octopus.TerraformPlan" + } else { + action.ActionType = "Octopus.TerraformApply" + } + expandTerraformTemplateAction(flattenedAction, action) return action } @@ -369,7 +383,7 @@ func flattenTerraformTemplateAzureAccount(properties map[string]octopusdeploy.Pr return []interface{}{flattenedMap} } -func flattenApplyTerraformTemplateAction(action *octopusdeploy.DeploymentAction) map[string]interface{} { +func flattenTerraformTemplateAction(action *octopusdeploy.DeploymentAction) map[string]interface{} { flattenedAction := flattenAction(action) for k, v := range action.Properties { @@ -401,9 +415,10 @@ func flattenApplyTerraformTemplateAction(action *octopusdeploy.DeploymentAction) return flattenedAction } -func getApplyTerraformTemplateActionSchema() *schema.Schema { +func getTerraformTemplateActionSchema() *schema.Schema { actionSchema, element := getActionSchema() addExecutionLocationSchema(element) + addTerraformTemplatePlanSchema(element) addTerraformTemplateAdvancedOptionsSchema(element) addTerraformTemplateAwsAccountSchema(element) addTerraformTemplateAzureAccountSchema(element) diff --git a/octopusdeploy/schema_action_apply_terraform_template_test.go b/octopusdeploy/schema_action_apply_terraform_template_test.go index ed1187060..b4cabaa00 100644 --- a/octopusdeploy/schema_action_apply_terraform_template_test.go +++ b/octopusdeploy/schema_action_apply_terraform_template_test.go @@ -18,6 +18,7 @@ func TestAccOctopusDeployApplyTerraformAction(t *testing.T) { name := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) pluginCacheDirectory := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) runOnServer := acctest.RandIntRange(0, 2) == 0 + isPlan := acctest.RandIntRange(0, 2) == 0 workspace := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) scriptSource := "Inline" @@ -45,19 +46,20 @@ func TestAccOctopusDeployApplyTerraformAction(t *testing.T) { Steps: []resource.TestStep{ { Check: resource.ComposeTestCheckFunc( - testAccCheckApplyTerraformAction(name, runOnServer, scriptSource, allowPluginDownloads, applyParameters, initParameters, pluginCacheDirectory, workspace, source, parameters), + testAccCheckApplyTerraformAction(name, runOnServer, isPlan, scriptSource, allowPluginDownloads, applyParameters, initParameters, pluginCacheDirectory, workspace, source, parameters), ), - Config: testAccApplyTerraformAction(name, runOnServer, scriptSource, allowPluginDownloads, applyParameters, initParameters, pluginCacheDirectory, workspace, source, parameters), + Config: testAccApplyTerraformAction(name, runOnServer, isPlan, scriptSource, allowPluginDownloads, applyParameters, initParameters, pluginCacheDirectory, workspace, source, parameters), }, }, }) } -func testAccApplyTerraformAction(name string, runOnServer bool, templateSource string, allowPluginDownloads bool, applyParameters string, initParameters string, pluginCacheDirectory string, workspace string, template string, templateParameters string) string { +func testAccApplyTerraformAction(name string, runOnServer bool, isPlan bool, templateSource string, allowPluginDownloads bool, applyParameters string, initParameters string, pluginCacheDirectory string, workspace string, template string, templateParameters string) string { return testAccBuildTestAction(fmt.Sprintf(` apply_terraform_template_action { name = "%s" run_on_server = %v + is_plan = %v template { additional_variable_files = "additional-variable-files" @@ -95,10 +97,10 @@ func testAccApplyTerraformAction(name string, runOnServer bool, templateSource s package_id = "MyPackage" feed_id = "feeds-builtin" } - }`, name, runOnServer, allowPluginDownloads, applyParameters, initParameters, pluginCacheDirectory, workspace)) + }`, name, runOnServer, isPlan, allowPluginDownloads, applyParameters, initParameters, pluginCacheDirectory, workspace)) } -func testAccCheckApplyTerraformAction(name string, runOnServer bool, scriptSource string, allowPluginDownloads bool, applyParameters string, initParameters string, pluginCacheDirectory string, workspace string, source string, parameters string) resource.TestCheckFunc { +func testAccCheckApplyTerraformAction(name string, runOnServer bool, isPlan bool, scriptSource string, allowPluginDownloads bool, applyParameters string, initParameters string, pluginCacheDirectory string, workspace string, source string, parameters string) resource.TestCheckFunc { return func(s *terraform.State) error { client := testAccProvider.Meta().(*octopusdeploy.Client) @@ -109,8 +111,12 @@ func testAccCheckApplyTerraformAction(name string, runOnServer bool, scriptSourc action := process.Steps[0].Actions[0] - if action.ActionType != "Octopus.TerraformApply" { - return fmt.Errorf("Action type is incorrect: %s", action.ActionType) + if !isPlan && action.ActionType != "Octopus.TerraformApply" { + return fmt.Errorf("Action type is incorrect: %s, isPlan: %s", action.ActionType, strconv.FormatBool(isPlan)) + } + + if isPlan && action.ActionType != "Octopus.TerraformPlan" { + return fmt.Errorf("Action type is incorrect: %s, isPlan: %s", action.ActionType, strconv.FormatBool(isPlan)) } if action.Properties["Octopus.Action.Terraform.AdditionalInitParams"].Value != initParameters { diff --git a/octopusdeploy/schema_action_destroy_terraform_template.go b/octopusdeploy/schema_action_destroy_terraform_template.go new file mode 100644 index 000000000..1faf7a8c4 --- /dev/null +++ b/octopusdeploy/schema_action_destroy_terraform_template.go @@ -0,0 +1,17 @@ +package octopusdeploy + +import ( + "github.com/OctopusDeploy/go-octopusdeploy/octopusdeploy" +) + +func expandDestroyTerraformTemplateAction(flattenedAction map[string]interface{}) *octopusdeploy.DeploymentAction { + action := expandAction(flattenedAction) + if isPlan, ok := flattenedAction["is_plan"].(bool); ok && isPlan { + action.ActionType = "Octopus.TerraformPlanDestroy" + } else { + action.ActionType = "Octopus.TerraformDestroy" + } + expandTerraformTemplateAction(flattenedAction, action) + + return action +} diff --git a/octopusdeploy/schema_action_destroy_terraform_template_test.go b/octopusdeploy/schema_action_destroy_terraform_template_test.go new file mode 100644 index 000000000..86c341279 --- /dev/null +++ b/octopusdeploy/schema_action_destroy_terraform_template_test.go @@ -0,0 +1,132 @@ +package octopusdeploy + +import ( + "fmt" + "strconv" + "testing" + + "github.com/OctopusDeploy/go-octopusdeploy/octopusdeploy" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccOctopusDeployDestroyTerraformAction(t *testing.T) { + allowPluginDownloads := acctest.RandIntRange(0, 2) == 0 + applyParameters := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + initParameters := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + name := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + pluginCacheDirectory := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + runOnServer := acctest.RandIntRange(0, 2) == 0 + isPlan := acctest.RandIntRange(0, 2) == 0 + workspace := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + + scriptSource := "Inline" + if acctest.RandIntRange(0, 2) == 0 { + scriptSource = "Package" + } + + parameters := "" + source := "" + if scriptSource == "Inline" { + variableName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + variableValue := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + source = fmt.Sprintf(`variable \"%s\" { type = string }`, variableName) + parameters = fmt.Sprintf(`{\"%s\":\"%s\"}`, variableName, variableValue) + } + + resource.Test(t, resource.TestCase{ + CheckDestroy: resource.ComposeTestCheckFunc( + testAccProjectCheckDestroy, + testAccProjectGroupCheckDestroy, + testAccLifecycleCheckDestroy, + ), + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Check: resource.ComposeTestCheckFunc( + testAccCheckDestroyTerraformAction(name, runOnServer, isPlan, scriptSource, allowPluginDownloads, applyParameters, initParameters, pluginCacheDirectory, workspace, source, parameters), + ), + Config: testAccDestroyTerraformAction(name, runOnServer, isPlan, scriptSource, allowPluginDownloads, applyParameters, initParameters, pluginCacheDirectory, workspace, source, parameters), + }, + }, + }) +} + +func testAccDestroyTerraformAction(name string, runOnServer bool, isPlan bool, templateSource string, allowPluginDownloads bool, applyParameters string, initParameters string, pluginCacheDirectory string, workspace string, template string, templateParameters string) string { + return testAccBuildTestAction(fmt.Sprintf(` + destroy_terraform_template_action { + name = "%s" + run_on_server = %v + is_plan = %v + + template { + additional_variable_files = "additional-variable-files" + directory = "template-directory" + run_automatic_file_substitution = false + target_files = "target-files" + } + + advanced_options { + allow_additional_plugin_downloads = %v + apply_parameters = "%s" + init_parameters = "%s" + plugin_cache_directory = "%s" + workspace = "%s" + } + + aws_account { + region = "us-east-1" + variable = "foo" + use_instance_role = true + + role { + arn = "arn" + external_id = "external-id" + role_session_name = "role-session-name" + session_duration = 1800 + } + } + + azure_account { + variable = "qwe" + } + + primary_package { + package_id = "MyPackage" + feed_id = "feeds-builtin" + } + }`, name, runOnServer, isPlan, allowPluginDownloads, applyParameters, initParameters, pluginCacheDirectory, workspace)) +} + +func testAccCheckDestroyTerraformAction(name string, runOnServer bool, isPlan bool, scriptSource string, allowPluginDownloads bool, applyParameters string, initParameters string, pluginCacheDirectory string, workspace string, source string, parameters string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := testAccProvider.Meta().(*octopusdeploy.Client) + + process, err := getDeploymentProcess(s, client) + if err != nil { + return err + } + + action := process.Steps[0].Actions[0] + + if !isPlan && action.ActionType != "Octopus.TerraformDestroy" { + return fmt.Errorf("Action type is incorrect: %s, isPlan: %s", action.ActionType, strconv.FormatBool(isPlan)) + } + + if isPlan && action.ActionType != "Octopus.TerraformPlanDestroy" { + return fmt.Errorf("Action type is incorrect: %s, isPlan: %s", action.ActionType, strconv.FormatBool(isPlan)) + } + + if action.Properties["Octopus.Action.Terraform.AdditionalInitParams"].Value != initParameters { + return fmt.Errorf("AdditionalInitParams: %s", action.Properties["Octopus.Action.Terraform.AdditionalInitParams"].Value) + } + + if v, _ := strconv.ParseBool(action.Properties["Octopus.Action.Terraform.AllowPluginDownloads"].Value); v != allowPluginDownloads { + return fmt.Errorf("AllowPluginDownloads: %s", action.Properties["Octopus.Action.Terraform.AllowPluginDownloads"].Value) + } + + return nil + } +} diff --git a/octopusdeploy/schema_deployment_step.go b/octopusdeploy/schema_deployment_step.go index 79c6852ec..1d8bf39d4 100644 --- a/octopusdeploy/schema_deployment_step.go +++ b/octopusdeploy/schema_deployment_step.go @@ -76,6 +76,13 @@ func expandDeploymentStep(flattenedStep map[string]interface{}) *octopusdeploy.D } } + if v, ok := flattenedStep["destroy_terraform_template_action"]; ok { + for _, tfAction := range v.([]interface{}) { + action := expandDestroyTerraformTemplateAction(tfAction.(map[string]interface{})) + step.Actions = append(step.Actions, *action) + } + } + if v, ok := flattenedStep["run_script_action"]; ok { for _, tfAction := range v.([]interface{}) { action := expandRunScriptAction(tfAction.(map[string]interface{})) @@ -138,8 +145,10 @@ func flattenDeploymentSteps(deploymentSteps []octopusdeploy.DeploymentStep) []ma flattenedDeploymentSteps[key]["run_script_action"] = []interface{}{flattenRunScriptAction(&deploymentStep.Actions[i])} case "Octopus.TentaclePackage": flattenedDeploymentSteps[key]["deploy_package_action"] = []interface{}{flattenDeployPackageAction(&deploymentStep.Actions[i])} - case "Octopus.TerraformApply": - flattenedDeploymentSteps[key]["apply_terraform_template_action"] = []interface{}{flattenApplyTerraformTemplateAction(&deploymentStep.Actions[i])} + case "Octopus.TerraformApply", "Octopus.TerraformPlan": + flattenedDeploymentSteps[key]["apply_terraform_template_action"] = []interface{}{flattenTerraformTemplateAction(&deploymentStep.Actions[i])} + case "Octopus.TerraformDestroy", "Octopus.TerraformPlanDestroy": + flattenedDeploymentSteps[key]["destroy_terraform_template_action"] = []interface{}{flattenTerraformTemplateAction(&deploymentStep.Actions[i])} case "Octopus.WindowsService": flattenedDeploymentSteps[key]["deploy_windows_service_action"] = []interface{}{flattenDeployWindowsServiceAction(&deploymentStep.Actions[i])} default: @@ -156,7 +165,7 @@ func getDeploymentStepSchema() *schema.Schema { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "action": getDeploymentActionSchema(), - "apply_terraform_template_action": getApplyTerraformTemplateActionSchema(), + "apply_terraform_template_action": getTerraformTemplateActionSchema(), "condition": { Default: "Success", Description: "When to run the step, one of 'Success', 'Failure', 'Always' or 'Variable'", @@ -175,12 +184,13 @@ func getDeploymentStepSchema() *schema.Schema { Optional: true, Type: schema.TypeString, }, - "deploy_kubernetes_secret_action": getDeployKubernetesSecretActionSchema(), - "deploy_package_action": getDeployPackageActionSchema(), - "deploy_windows_service_action": getDeployWindowsServiceActionSchema(), - "id": getIDSchema(), - "manual_intervention_action": getManualInterventionActionSchema(), - "name": getNameSchema(true), + "deploy_kubernetes_secret_action": getDeployKubernetesSecretActionSchema(), + "deploy_package_action": getDeployPackageActionSchema(), + "deploy_windows_service_action": getDeployWindowsServiceActionSchema(), + "destroy_terraform_template_action": getTerraformTemplateActionSchema(), + "id": getIDSchema(), + "manual_intervention_action": getManualInterventionActionSchema(), + "name": getNameSchema(true), "package_requirement": { Default: "LetOctopusDecide", Description: "Whether to run this step before or after package acquisition (if possible)",