From 080f778a500eb3b6bdba47235a0647caedb39df3 Mon Sep 17 00:00:00 2001 From: Glenn Sarti Date: Tue, 19 Jul 2022 17:24:18 +0800 Subject: [PATCH] Add stage to workspace run task resource and data source This commit adds the stage attribute to the Workspace Run Task Resource and Data Source. Note that this is in beta for the Resource. This commit adds acceptance tests, and optionally test the Beta features. This commit also adds descriptions for the Workspace Run Task attributes. --- CHANGELOG.md | 1 + tfe/data_source_workspace_run_task.go | 22 +++-- tfe/data_source_workspace_run_task_test.go | 1 + tfe/resource_tfe_workspace_run_task.go | 73 +++++++++++++-- tfe/resource_tfe_workspace_run_task_test.go | 88 +++++++++++++++++++ tfe/testing.go | 11 +++ .../docs/d/workspace_run_task.html.markdown | 3 +- .../docs/r/workspace_run_task.html.markdown | 1 + 8 files changed, 186 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da8ce2b20..bd99f1a53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ FEATURES: * r/tfe_organization_run_task, d/tfe_organization_run_task: Add `description` attribute to organization run tasks. ([#585](https://github.com/hashicorp/terraform-provider-tfe/pull/585)) * r/tfe_workspace: Adds `tags_regex` attribute to `vcs_repo` for workspaces, enabling a workspace to trigger runs for matching Git tags. ([#549](https://github.com/hashicorp/terraform-provider-tfe/pull/549)) * r/agent_pool: Agent Pools can now be imported using `/` ([#561](https://github.com/hashicorp/terraform-provider-tfe/pull/561)) +* 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)) BUG FIXES: * d/tfe_outputs: Fix a bug causing sensitive values to be missing from tfe_outputs ([#565](https://github.com/hashicorp/terraform-provider-tfe/pull/565)) 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