diff --git a/CHANGELOG.md b/CHANGELOG.md index da8ce2b20..a95489f31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## Unreleased + +FEATURES: +* r/tfe_workspace_run_task, d/tfe_workspace_run_task: Add `stage` attribute to workspace run tasks. ([#555](https://github.com/hashicorp/terraform-provider-tfe/pull/555)) + ## v0.36.0 (August 16th, 2022) FEATURES: diff --git a/tfe/data_source_workspace_run_task.go b/tfe/data_source_workspace_run_task.go index 0c5b9862a..f780a8a23 100644 --- a/tfe/data_source_workspace_run_task.go +++ b/tfe/data_source_workspace_run_task.go @@ -13,18 +13,27 @@ func dataSourceTFEWorkspaceRunTask() *schema.Resource { Schema: map[string]*schema.Schema{ "workspace_id": { - Type: schema.TypeString, - Required: true, + Description: "The id of the workspace.", + Type: schema.TypeString, + Required: true, }, "task_id": { - Type: schema.TypeString, - Required: true, + Description: "The id of the run task.", + Type: schema.TypeString, + Required: true, }, "enforcement_level": { - Type: schema.TypeString, - Computed: true, + Description: "The enforcement level of the task.", + Type: schema.TypeString, + Computed: true, + }, + + "stage": { + Description: "Which stage the task will run in.", + Type: schema.TypeString, + Computed: true, }, }, } @@ -47,6 +56,7 @@ func dataSourceTFEWorkspaceRunTaskRead(d *schema.ResourceData, meta interface{}) for _, wstask := range list.Items { if wstask.RunTask.ID == taskID { d.Set("enforcement_level", string(wstask.EnforcementLevel)) + d.Set("stage", string(wstask.Stage)) d.SetId(wstask.ID) return nil } diff --git a/tfe/data_source_workspace_run_task_test.go b/tfe/data_source_workspace_run_task_test.go index a7356e274..841c0cb30 100644 --- a/tfe/data_source_workspace_run_task_test.go +++ b/tfe/data_source_workspace_run_task_test.go @@ -25,6 +25,7 @@ func TestAccTFEWorkspaceRunTaskDataSource_basic(t *testing.T) { Config: testAccTFEWorkspaceRunTaskDataSourceConfig(orgName, rInt, runTasksURL()), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("data.tfe_workspace_run_task.foobar", "enforcement_level", "advisory"), + resource.TestCheckResourceAttrSet("data.tfe_workspace_run_task.foobar", "stage"), resource.TestCheckResourceAttrSet("data.tfe_workspace_run_task.foobar", "id"), resource.TestCheckResourceAttrSet("data.tfe_workspace_run_task.foobar", "task_id"), resource.TestCheckResourceAttrSet("data.tfe_workspace_run_task.foobar", "workspace_id"), diff --git a/tfe/resource_tfe_workspace_run_task.go b/tfe/resource_tfe_workspace_run_task.go index d9775bfe2..46a390adb 100644 --- a/tfe/resource_tfe_workspace_run_task.go +++ b/tfe/resource_tfe_workspace_run_task.go @@ -10,6 +10,36 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) +func workspaceRunTaskEnforcementLevels() []string { + return []string{ + string(tfe.Advisory), + string(tfe.Mandatory), + } +} + +func workspaceRunTaskStages() []string { + return []string{ + string(tfe.PrePlan), + string(tfe.PostPlan), + } +} + +// Helper function to turn a slice of strings into an english sentence for documentation +func sentenceList(items []string, prefix string, suffix string, conjunction string) string { + var b strings.Builder + for i, v := range items { + fmt.Fprint(&b, prefix, v, suffix) + if i < len(items)-1 { + if i < len(items)-2 { + fmt.Fprint(&b, ", ") + } else { + fmt.Fprintf(&b, " %s ", conjunction) + } + } + } + return b.String() +} + func resourceTFEWorkspaceRunTask() *schema.Resource { return &schema.Resource{ Create: resourceTFEWorkspaceRunTaskCreate, @@ -22,25 +52,47 @@ func resourceTFEWorkspaceRunTask() *schema.Resource { Schema: map[string]*schema.Schema{ "workspace_id": { - Type: schema.TypeString, - ForceNew: true, - Required: true, + Description: "The id of the workspace to associate the Run task to.", + Type: schema.TypeString, + ForceNew: true, + Required: true, }, "task_id": { + Description: "The id of the Run task to associate to the Workspace.", + Type: schema.TypeString, ForceNew: true, Required: true, }, "enforcement_level": { + Description: fmt.Sprintf("The enforcement level of the task. Valid values are %s.", sentenceList( + workspaceRunTaskEnforcementLevels(), + "`", + "`", + "and", + )), Type: schema.TypeString, Required: true, ValidateFunc: validation.StringInSlice( - []string{ - string(tfe.Advisory), - string(tfe.Mandatory), - }, + workspaceRunTaskEnforcementLevels(), + false, + ), + }, + + "stage": { + Description: fmt.Sprintf("This is currently in BETA. The stage to run the task in. Valid values are %s.", sentenceList( + workspaceRunTaskStages(), + "`", + "`", + "and", + )), + Type: schema.TypeString, + Optional: true, + Default: tfe.PostPlan, + ValidateFunc: validation.StringInSlice( + workspaceRunTaskStages(), false, ), }, @@ -65,10 +117,12 @@ func resourceTFEWorkspaceRunTaskCreate(d *schema.ResourceData, meta interface{}) return fmt.Errorf( "Error retrieving workspace %s: %w", workspaceID, err) } + stage := tfe.Stage(d.Get("stage").(string)) options := tfe.WorkspaceRunTaskCreateOptions{ RunTask: task, EnforcementLevel: tfe.TaskEnforcementLevel(d.Get("enforcement_level").(string)), + Stage: &stage, } log.Printf("[DEBUG] Create task %s in workspace %s", task.ID, ws.ID) @@ -108,6 +162,10 @@ func resourceTFEWorkspaceRunTaskUpdate(d *schema.ResourceData, meta interface{}) if d.HasChange("enforcement_level") { options.EnforcementLevel = tfe.TaskEnforcementLevel(d.Get("enforcement_level").(string)) } + if d.HasChange("stage") { + stage := tfe.Stage(d.Get("stage").(string)) + options.Stage = &stage + } log.Printf("[DEBUG] Update configuration of task %s in workspace %s", d.Id(), workspaceID) _, err := tfeClient.WorkspaceRunTasks.Update(ctx, workspaceID, d.Id(), options) @@ -138,6 +196,7 @@ func resourceTFEWorkspaceRunTaskRead(d *schema.ResourceData, meta interface{}) e d.Set("workspace_id", wstask.Workspace.ID) d.Set("task_id", wstask.RunTask.ID) d.Set("enforcement_level", string(wstask.EnforcementLevel)) + d.Set("stage", string(wstask.Stage)) return nil } diff --git a/tfe/resource_tfe_workspace_run_task_test.go b/tfe/resource_tfe_workspace_run_task_test.go index 76d6a67a0..80e23cb36 100644 --- a/tfe/resource_tfe_workspace_run_task_test.go +++ b/tfe/resource_tfe_workspace_run_task_test.go @@ -42,6 +42,40 @@ func TestAccTFEWorkspaceRunTask_create(t *testing.T) { }) } +func TestAccTFEWorkspaceRunTask_beta_create(t *testing.T) { + skipUnlessRunTasksDefined(t) + skipUnlessBeta(t) + skipIfFreeOnly(t) // Run Tasks requires TFE or a TFC paid/trial subscription + + workspaceTask := &tfe.WorkspaceRunTask{} + rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int() + orgName := fmt.Sprintf("tst-terraform-%d", rInt) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckTFEWorkspaceRunTaskDestroy, + Steps: []resource.TestStep{ + testCheckCreateOrgWithRunTasks(orgName), + { + Config: testAccTFEWorkspaceRunTask_beta_basic(orgName, runTasksURL()), + Check: resource.ComposeTestCheckFunc( + testAccCheckTFEWorkspaceRunTaskExists("tfe_workspace_run_task.foobar", workspaceTask), + resource.TestCheckResourceAttr("tfe_workspace_run_task.foobar", "enforcement_level", "advisory"), + resource.TestCheckResourceAttr("tfe_workspace_run_task.foobar", "stage", "post_plan"), + ), + }, + { + Config: testAccTFEWorkspaceRunTask_beta_update(orgName, runTasksURL()), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("tfe_workspace_run_task.foobar", "enforcement_level", "mandatory"), + resource.TestCheckResourceAttr("tfe_workspace_run_task.foobar", "stage", "pre_plan"), + ), + }, + }, + }) +} + func TestAccTFEWorkspaceRunTask_import(t *testing.T) { skipUnlessRunTasksDefined(t) skipIfFreeOnly(t) // Run Tasks requires TFE or a TFC paid/trial subscription @@ -175,3 +209,57 @@ resource "tfe_workspace_run_task" "foobar" { } `, orgName, runTaskURL) } + +func testAccTFEWorkspaceRunTask_beta_basic(orgName, runTaskURL string) string { + return fmt.Sprintf(` +resource "tfe_organization" "foobar" { + name = "%s" + email = "admin@company.com" +} + +resource "tfe_organization_run_task" "foobar" { + organization = tfe_organization.foobar.id + url = "%s" + name = "foobar-task" +} + +resource "tfe_workspace" "foobar" { + name = "workspace-test" + organization = tfe_organization.foobar.id +} + +resource "tfe_workspace_run_task" "foobar" { + workspace_id = resource.tfe_workspace.foobar.id + task_id = resource.tfe_organization_run_task.foobar.id + enforcement_level = "advisory" + stage = "post_plan" +} +`, orgName, runTaskURL) +} + +func testAccTFEWorkspaceRunTask_beta_update(orgName, runTaskURL string) string { + return fmt.Sprintf(` +resource "tfe_organization" "foobar" { + name = "%s" + email = "admin@company.com" +} + +resource "tfe_organization_run_task" "foobar" { + organization = tfe_organization.foobar.id + url = "%s" + name = "foobar-task" +} + +resource "tfe_workspace" "foobar" { + name = "workspace-test" + organization = tfe_organization.foobar.id +} + +resource "tfe_workspace_run_task" "foobar" { + workspace_id = resource.tfe_workspace.foobar.id + task_id = resource.tfe_organization_run_task.foobar.id + enforcement_level = "mandatory" + stage = "pre_plan" +} +`, orgName, runTaskURL) +} diff --git a/tfe/testing.go b/tfe/testing.go index 491af9cb8..a01121672 100644 --- a/tfe/testing.go +++ b/tfe/testing.go @@ -67,6 +67,12 @@ func skipUnlessRunTasksDefined(t *testing.T) { } } +func skipUnlessBeta(t *testing.T) { + if !betaFeaturesEnabled() { + t.Skip("Skipping test related to a Terraform Cloud/Enterprise beta feature. Set ENABLE_BETA=1 to run.") + } +} + func enterpriseEnabled() bool { return os.Getenv("ENABLE_TFE") == "1" } @@ -79,6 +85,11 @@ func runTasksURL() string { return os.Getenv(RunTasksURLEnvName) } +// Checks to see if ENABLE_BETA is set to 1, thereby enabling tests for beta features. +func betaFeaturesEnabled() bool { + return os.Getenv("ENABLE_BETA") == "1" +} + // Most tests rely on terraform-plugin-sdk/helper/resource.Test to run. That test helper ensures // that TF_ACC=1 or else it skips. In some rare cases, however, tests do not use the SDK helper and // are acceptance tests. diff --git a/website/docs/d/workspace_run_task.html.markdown b/website/docs/d/workspace_run_task.html.markdown index db7dee308..422e15520 100644 --- a/website/docs/d/workspace_run_task.html.markdown +++ b/website/docs/d/workspace_run_task.html.markdown @@ -25,7 +25,7 @@ data "tfe_workspace_run_task" "foobar" { The following arguments are supported: -* `task_id` - (Required) The id of the Run task. +* `task_id` - (Required) The id of the run task. * `workspace_id` - (Required) The id of the workspace. ## Attributes Reference @@ -34,3 +34,4 @@ In addition to all arguments above, the following attributes are exported: * `enforcement_level` - The enforcement level of the task. * `id` - The ID of the Workspace Run task. +* `stage` - Which stage the task will run in. diff --git a/website/docs/r/workspace_run_task.html.markdown b/website/docs/r/workspace_run_task.html.markdown index 4e23e99b8..b8e80ecb8 100644 --- a/website/docs/r/workspace_run_task.html.markdown +++ b/website/docs/r/workspace_run_task.html.markdown @@ -31,6 +31,7 @@ The following arguments are supported: * `enforcement_level` - (Required) The enforcement level of the task. Valid values are `advisory` and `mandatory`. * `task_id` - (Required) The id of the Run task to associate to the Workspace. * `workspace_id` - (Required) The id of the workspace to associate the Run task to. +* `stage` - (Optional) This is currently in BETA. The stage to run the task in. Valid values are `pre-plan` and `post-plan`. ## Attributes Reference