diff --git a/.github/actions/test-provider-tfe/action.yml b/.github/actions/test-provider-tfe/action.yml index 9a10d5cac..b6b48a3c4 100644 --- a/.github/actions/test-provider-tfe/action.yml +++ b/.github/actions/test-provider-tfe/action.yml @@ -102,7 +102,7 @@ runs: TFE_USER2: tfe-provider-user2 TF_ACC: "1" ENABLE_TFE: "${{ inputs.enterprise }}" - TFC_RUN_TASK_URL: "https://httpstat.us/200" + RUN_TASKS_URL: "http://testing-mocks.tfe:22180/runtasks/pass" GITHUB_POLICY_SET_IDENTIFIER: "hashicorp/test-policy-set" GITHUB_REGISTRY_MODULE_IDENTIFIER: "hashicorp/terraform-random-module" GITHUB_WORKSPACE_IDENTIFIER: "hashicorp/terraform-random-module" diff --git a/docs/testing.md b/docs/testing.md index c61a56bda..3b977d5cf 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -45,8 +45,9 @@ these values with the environment variables specified below: 8. `GITHUB_WORKSPACE_BRANCH`: A GitHub branch for the repository specified by `GITHUB_WORKSPACE_IDENTIFIER`. Required for running workspace tests. 9. `ENABLE_TFE` - Some tests cover features available only in Terraform Cloud. To skip these tests when running against a Terraform Enterprise instance, set `ENABLE_TFE=1`. 10. `RUN_TASKS_URL` - External URL to use for testing Run Tasks operations, for example `RUN_TASKS_URL=http://somewhere.local:8080/pass`. Required for running run tasks tests. -11. `GITHUB_APP_INSTALLATION_ID` - GitHub App installation internal id in the format `ghain-xxxxxxx`. Required for running any tests that use GitHub App VCS (workspace, policy sets, registry module). -12. `GITHUB_APP_INSTALLATION_NAME` - GitHub App installation name. Required for running tfe_github_app_installation data source test. +11. `RUN_TASKS_HMAC` - The optional HMAC Key that should be used for Run Task operations. The default is no key. +12. `GITHUB_APP_INSTALLATION_ID` - GitHub App installation internal id in the format `ghain-xxxxxxx`. Required for running any tests that use GitHub App VCS (workspace, policy sets, registry module). +13. `GITHUB_APP_INSTALLATION_NAME` - GitHub App installation name. Required for running tfe_github_app_installation data source test. **Note:** In order to run integration tests for **Paid** features you will need a token `TFE_TOKEN` with TFC/E administrator privileges, otherwise the attempt to upgrade an organization's feature set will fail. diff --git a/internal/provider/admin_helpers_test.go b/internal/provider/admin_helpers_test.go new file mode 100644 index 000000000..7bb9b3b86 --- /dev/null +++ b/internal/provider/admin_helpers_test.go @@ -0,0 +1,59 @@ +package provider + +import ( + "os" + "testing" + + tfe "github.com/hashicorp/go-tfe" +) + +type adminRoleType string + +const ( + siteAdmin adminRoleType = "site-admin" + configurationAdmin adminRoleType = "configuration" + provisionLicensesAdmin adminRoleType = "provision-licenses" + subscriptionAdmin adminRoleType = "subscription" + supportAdmin adminRoleType = "support" + securityMaintenanceAdmin adminRoleType = "security-maintenance" + versionMaintenanceAdmin adminRoleType = "version-maintenance" +) + +func getTokenForAdminRole(adminRole adminRoleType) string { + token := "" + + switch adminRole { + case siteAdmin: + token = os.Getenv("TFE_ADMIN_SITE_ADMIN_TOKEN") + case configurationAdmin: + token = os.Getenv("TFE_ADMIN_CONFIGURATION_TOKEN") + case provisionLicensesAdmin: + token = os.Getenv("TFE_ADMIN_PROVISION_LICENSES_TOKEN") + case subscriptionAdmin: + token = os.Getenv("TFE_ADMIN_SUBSCRIPTION_TOKEN") + case supportAdmin: + token = os.Getenv("TFE_ADMIN_SUPPORT_TOKEN") + case securityMaintenanceAdmin: + token = os.Getenv("TFE_ADMIN_SECURITY_MAINTENANCE_TOKEN") + case versionMaintenanceAdmin: + token = os.Getenv("TFE_ADMIN_VERSION_MAINTENANCE_TOKEN") + } + + return token +} + +func testAdminClient(t *testing.T, adminRole adminRoleType) *tfe.Client { + token := getTokenForAdminRole(adminRole) + if token == "" { + t.Fatal("missing API token for admin role " + adminRole) + } + + client, err := tfe.NewClient(&tfe.Config{ + Token: token, + }) + if err != nil { + t.Fatal(err) + } + + return client +} diff --git a/internal/provider/client_mock_workspaces.go b/internal/provider/client_mock_workspaces_test.go similarity index 100% rename from internal/provider/client_mock_workspaces.go rename to internal/provider/client_mock_workspaces_test.go diff --git a/internal/provider/data_source_organization_run_task_test.go b/internal/provider/data_source_organization_run_task_test.go index 7159fcf38..cd2bcea15 100644 --- a/internal/provider/data_source_organization_run_task_test.go +++ b/internal/provider/data_source_organization_run_task_test.go @@ -30,7 +30,7 @@ func TestAccTFEOrganizationRunTaskDataSource_basic(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccTFEOrganizationRunTaskDataSourceConfig(org.Name, rInt, runTasksURL()), + Config: testAccTFEOrganizationRunTaskDataSourceConfig(org.Name, rInt, runTasksURL(), runTasksHMACKey()), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("data.tfe_organization_run_task.foobar", "name", fmt.Sprintf("foobar-task-%d", rInt)), resource.TestCheckResourceAttr("data.tfe_organization_run_task.foobar", "url", runTasksURL()), @@ -45,7 +45,7 @@ func TestAccTFEOrganizationRunTaskDataSource_basic(t *testing.T) { }) } -func testAccTFEOrganizationRunTaskDataSourceConfig(orgName string, rInt int, runTaskURL string) string { +func testAccTFEOrganizationRunTaskDataSourceConfig(orgName string, rInt int, runTaskURL, runTaskHMACKey string) string { return fmt.Sprintf(` locals { organization_name = "%s" @@ -55,7 +55,7 @@ resource "tfe_organization_run_task" "foobar" { organization = local.organization_name url = "%s" name = "foobar-task-%d" - hmac_key = "Password1" + hmac_key = "%s" enabled = false description = "a description" } @@ -64,5 +64,5 @@ data "tfe_organization_run_task" "foobar" { organization = local.organization_name name = "foobar-task-%d" depends_on = [tfe_organization_run_task.foobar] -}`, orgName, runTaskURL, rInt, rInt) +}`, orgName, runTaskURL, rInt, runTaskHMACKey, rInt) } diff --git a/internal/provider/data_source_policy_set_test.go b/internal/provider/data_source_policy_set_test.go index ab2691008..d94732c03 100644 --- a/internal/provider/data_source_policy_set_test.go +++ b/internal/provider/data_source_policy_set_test.go @@ -13,8 +13,13 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) +// Known Tool Versions that will exist in the TFE/TFC instance that's +// being used for acceptance testing. Once the /api/v2/tool-versions endpoint +// is available in go-tfe, we can retrieve this dynamically. +const KnownOPAToolVersion = "0.44.0" +const KnownSentinelToolVersion = "0.22.1" + func TestAccTFEPolicySetDataSource_basic(t *testing.T) { - skipUnlessBeta(t) tfeClient, err := getClientUsingEnv() if err != nil { t.Fatal(err) @@ -66,9 +71,6 @@ func TestAccTFEPolicySetDataSource_pinnedPolicyRuntimeVersion(t *testing.T) { t.Fatal(err) } - sha := genSentinelSha(t, "secret", "data") - version := genSafeRandomSentinelVersion() - org, orgCleanup := createBusinessOrganization(t, tfeClient) t.Cleanup(orgCleanup) @@ -80,7 +82,7 @@ func TestAccTFEPolicySetDataSource_pinnedPolicyRuntimeVersion(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccTFEPolicySetDataSourceConfig_pinnedPolicyRuntimeVersion(org.Name, rInt, version, sha), + Config: testAccTFEPolicySetDataSourceConfig_pinnedPolicyRuntimeVersion(org.Name, rInt, KnownSentinelToolVersion), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrSet("data.tfe_policy_set.bar", "id"), resource.TestCheckResourceAttr( @@ -96,7 +98,7 @@ func TestAccTFEPolicySetDataSource_pinnedPolicyRuntimeVersion(t *testing.T) { resource.TestCheckResourceAttr( "data.tfe_policy_set.bar", "agent_enabled", "true"), resource.TestCheckResourceAttr( - "data.tfe_policy_set.bar", "policy_tool_version", version), + "data.tfe_policy_set.bar", "policy_tool_version", KnownSentinelToolVersion), resource.TestCheckResourceAttr( "data.tfe_policy_set.bar", "workspace_ids.#", "1"), resource.TestCheckResourceAttr( @@ -113,15 +115,11 @@ func TestAccTFEPolicySetDataSource_pinnedPolicyRuntimeVersion(t *testing.T) { } func TestAccTFEPolicySetDataSourceOPA_basic(t *testing.T) { - skipUnlessBeta(t) tfeClient, err := getClientUsingEnv() if err != nil { t.Fatal(err) } - sha := genSentinelSha(t, "secret", "data") - version := genSafeRandomOPAVersion() - org, orgCleanup := createBusinessOrganization(t, tfeClient) t.Cleanup(orgCleanup) @@ -133,7 +131,7 @@ func TestAccTFEPolicySetDataSourceOPA_basic(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccTFEPolicySetDataSourceConfigOPA_basic(org.Name, rInt, version, sha), + Config: testAccTFEPolicySetDataSourceConfigOPA_basic(org.Name, rInt, KnownOPAToolVersion), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrSet("data.tfe_policy_set.bar", "id"), resource.TestCheckResourceAttr( @@ -291,19 +289,12 @@ data "tfe_policy_set" "bar" { }`, organization, rInt, rInt, rInt) } -func testAccTFEPolicySetDataSourceConfig_pinnedPolicyRuntimeVersion(organization string, rInt int, version string, sha string) string { +func testAccTFEPolicySetDataSourceConfig_pinnedPolicyRuntimeVersion(organization string, rInt int, version string) string { return fmt.Sprintf(` locals { organization_name = "%s" } - -resource "tfe_sentinel_version" "foobar" { - version = "%s" - url = "https://www.hashicorp.com" - sha = "%s" -} - resource "tfe_workspace" "foobar" { name = "workspace-foo-%d" organization = local.organization_name @@ -344,22 +335,15 @@ data "tfe_policy_set" "bar" { name = tfe_policy_set.foobar.name organization = local.organization_name depends_on=[tfe_policy_set.foobar, tfe_project_policy_set.foobar, tfe_workspace_policy_set_exclusion.foobar] -}`, organization, version, sha, rInt, rInt, rInt, version) +}`, organization, rInt, rInt, rInt, version) } -func testAccTFEPolicySetDataSourceConfigOPA_basic(organization string, rInt int, version string, sha string) string { +func testAccTFEPolicySetDataSourceConfigOPA_basic(organization string, rInt int, version string) string { return fmt.Sprintf(` locals { organization_name = "%s" } - -resource "tfe_opa_version" "foobar" { - version = "%s" - url = "https://www.hashicorp.com" - sha = "%s" -} - resource "tfe_workspace" "foobar" { name = "workspace-foo-%d" organization = local.organization_name @@ -396,7 +380,7 @@ data "tfe_policy_set" "bar" { organization = local.organization_name kind = "opa" depends_on=[tfe_policy_set.foobar, tfe_project_policy_set.foobar, tfe_workspace_policy_set_exclusion.foobar] -}`, organization, version, sha, rInt, rInt, rInt, version) +}`, organization, rInt, rInt, rInt, version) } func testAccTFEPolicySetDataSourceConfig_vcs(organization string, rInt int) string { diff --git a/internal/provider/testing.go b/internal/provider/helper_test.go similarity index 79% rename from internal/provider/testing.go rename to internal/provider/helper_test.go index 8c428510f..7d3bcc395 100644 --- a/internal/provider/testing.go +++ b/internal/provider/helper_test.go @@ -6,7 +6,6 @@ package provider import ( "context" "fmt" - "net/url" "os" "testing" "time" @@ -18,6 +17,7 @@ import ( ) const RunTasksURLEnvName = "RUN_TASKS_URL" +const RunTasksHMACKeyEnvName = "RUN_TASKS_HMAC" type testClientOptions struct { defaultOrganization string @@ -25,29 +25,6 @@ type testClientOptions struct { remoteStateConsumersResponse string } -type featureSet struct { - ID string `jsonapi:"primary,feature-sets"` -} - -type featureSetList struct { - Items []*featureSet - *tfe.Pagination -} - -type featureSetListOptions struct { - Q string `url:"q,omitempty"` -} - -type updateFeatureSetOptions struct { - Type string `jsonapi:"primary,subscription"` - RunsCeiling int `jsonapi:"attr,runs-ceiling"` - ContractStartAt time.Time `jsonapi:"attr,contract-start-at,iso8601"` - ContractUserLimit int `jsonapi:"attr,contract-user-limit"` - ContractApplyLimit int `jsonapi:"attr,contract-apply-limit"` - - FeatureSet *featureSet `jsonapi:"relation,feature-set"` -} - // testTfeClient creates a mock client that creates workspaces with their ID // set to workspaceID. func testTfeClient(t *testing.T, options testClientOptions) *tfe.Client { @@ -72,50 +49,10 @@ func testTfeClient(t *testing.T, options testClientOptions) *tfe.Client { return client } -func upgradeOrganizationSubscription(t *testing.T, client *tfe.Client, org *tfe.Organization) { - if enterpriseEnabled() { - t.Skip("Cannot upgrade an organization's subscription when enterprise is enabled. Set ENABLE_TFE=0 to run.") - } - - req, err := client.NewRequest("GET", "admin/feature-sets", featureSetListOptions{ - Q: "Business", - }) - if err != nil { - t.Fatal(err) - return - } - - fsl := &featureSetList{} - err = req.Do(context.Background(), fsl) - if err != nil { - t.Fatalf("failed to enumerate feature sets: %v", err) - return - } else if len(fsl.Items) == 0 { - // this will serve as our catch all if enterprise is not enabled - // but the instance itself is enterprise - t.Fatalf("feature set response was empty") - return - } - - opts := updateFeatureSetOptions{ - RunsCeiling: 10, - ContractStartAt: time.Now(), - ContractUserLimit: 1000, - ContractApplyLimit: 5000, - FeatureSet: fsl.Items[0], - } - - u := fmt.Sprintf("admin/organizations/%s/subscription", url.QueryEscape(org.Name)) - req, err = client.NewRequest("POST", u, &opts) - if err != nil { - t.Fatalf("Failed to create request: %v", err) - return - } - - err = req.Do(context.Background(), nil) - if err != nil { - t.Fatalf("Failed to upgrade subscription: %v", err) - } +// Attempts to upgrade an organization to the business plan. Requires a user token with admin access. +// DEPRECATED : Please use the newSubscriptionUpdater instead. +func upgradeOrganizationSubscription(t *testing.T, _ *tfe.Client, organization *tfe.Organization) { + newSubscriptionUpdater(organization).WithBusinessPlan().Update(t) } func createBusinessOrganization(t *testing.T, client *tfe.Client) (*tfe.Organization, func()) { @@ -124,7 +61,7 @@ func createBusinessOrganization(t *testing.T, client *tfe.Client) (*tfe.Organiza Email: tfe.String(fmt.Sprintf("%s@tfe.local", randomString(t))), }) - upgradeOrganizationSubscription(t, client, org) + newSubscriptionUpdater(org).WithBusinessPlan().Update(t) return org, orgCleanup } @@ -275,6 +212,10 @@ func runTasksURL() string { return os.Getenv(RunTasksURLEnvName) } +func runTasksHMACKey() string { + return os.Getenv(RunTasksHMACKeyEnvName) +} + // 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" diff --git a/internal/provider/resource_tfe_organization_run_task_test.go b/internal/provider/resource_tfe_organization_run_task_test.go index 27ea14ed0..2995b93b6 100644 --- a/internal/provider/resource_tfe_organization_run_task_test.go +++ b/internal/provider/resource_tfe_organization_run_task_test.go @@ -21,15 +21,15 @@ func TestAccTFEOrganizationRunTask_validateSchemaAttributeUrl(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccTFEOrganizationRunTask_basic("org", 1, ""), + Config: testAccTFEOrganizationRunTask_basic("org", 1, "", ""), ExpectError: regexp.MustCompile(`url to not be empty`), }, { - Config: testAccTFEOrganizationRunTask_basic("org", 1, "https://"), + Config: testAccTFEOrganizationRunTask_basic("org", 1, "https://", ""), ExpectError: regexp.MustCompile(`to have a host`), }, { - Config: testAccTFEOrganizationRunTask_basic("org", 1, "ftp://a.valid.url/path"), + Config: testAccTFEOrganizationRunTask_basic("org", 1, "ftp://a.valid.url/path", ""), ExpectError: regexp.MustCompile(`to have a url with schema of: "http,https"`), }, }, @@ -50,30 +50,35 @@ func TestAccTFEOrganizationRunTask_create(t *testing.T) { runTask := &tfe.RunTask{} rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int() + // Note - We cannot easily test updating the HMAC Key as that would require coordination between this test suite + // and the external Run Task service to "magically" allow a different Key. Instead we "update" with the same key + // and manually test HMAC Key changes. + hmacKey := runTasksHMACKey() + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckTFEOrganizationRunTaskDestroy, Steps: []resource.TestStep{ { - Config: testAccTFEOrganizationRunTask_basic(org.Name, rInt, runTasksURL()), + Config: testAccTFEOrganizationRunTask_basic(org.Name, rInt, runTasksURL(), hmacKey), Check: resource.ComposeTestCheckFunc( testAccCheckTFEOrganizationRunTaskExists("tfe_organization_run_task.foobar", runTask), resource.TestCheckResourceAttr("tfe_organization_run_task.foobar", "name", fmt.Sprintf("foobar-task-%d", rInt)), resource.TestCheckResourceAttr("tfe_organization_run_task.foobar", "url", runTasksURL()), resource.TestCheckResourceAttr("tfe_organization_run_task.foobar", "category", "task"), - resource.TestCheckResourceAttr("tfe_organization_run_task.foobar", "hmac_key", ""), + resource.TestCheckResourceAttr("tfe_organization_run_task.foobar", "hmac_key", hmacKey), resource.TestCheckResourceAttr("tfe_organization_run_task.foobar", "enabled", "false"), resource.TestCheckResourceAttr("tfe_organization_run_task.foobar", "description", ""), ), }, { - Config: testAccTFEOrganizationRunTask_update(org.Name, rInt, runTasksURL()), + Config: testAccTFEOrganizationRunTask_update(org.Name, rInt, runTasksURL(), hmacKey), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("tfe_organization_run_task.foobar", "name", fmt.Sprintf("foobar-task-%d-new", rInt)), resource.TestCheckResourceAttr("tfe_organization_run_task.foobar", "url", runTasksURL()), resource.TestCheckResourceAttr("tfe_organization_run_task.foobar", "category", "task"), - resource.TestCheckResourceAttr("tfe_organization_run_task.foobar", "hmac_key", "somepassword"), + resource.TestCheckResourceAttr("tfe_organization_run_task.foobar", "hmac_key", hmacKey), resource.TestCheckResourceAttr("tfe_organization_run_task.foobar", "enabled", "true"), resource.TestCheckResourceAttr("tfe_organization_run_task.foobar", "description", "a description"), ), @@ -101,7 +106,7 @@ func TestAccTFEOrganizationRunTask_import(t *testing.T) { CheckDestroy: testAccCheckTFETeamAccessDestroy, Steps: []resource.TestStep{ { - Config: testAccTFEOrganizationRunTask_basic(org.Name, rInt, runTasksURL()), + Config: testAccTFEOrganizationRunTask_basic(org.Name, rInt, runTasksURL(), runTasksHMACKey()), }, { ResourceName: "tfe_organization_run_task.foobar", @@ -161,26 +166,27 @@ func testAccCheckTFEOrganizationRunTaskDestroy(s *terraform.State) error { return nil } -func testAccTFEOrganizationRunTask_basic(orgName string, rInt int, runTaskURL string) string { +func testAccTFEOrganizationRunTask_basic(orgName string, rInt int, runTaskURL, runTaskHMACKey string) string { return fmt.Sprintf(` resource "tfe_organization_run_task" "foobar" { organization = "%s" url = "%s" name = "foobar-task-%d" enabled = false + hmac_key = "%s" } -`, orgName, runTaskURL, rInt) +`, orgName, runTaskURL, rInt, runTaskHMACKey) } -func testAccTFEOrganizationRunTask_update(orgName string, rInt int, runTaskURL string) string { +func testAccTFEOrganizationRunTask_update(orgName string, rInt int, runTaskURL, runTaskHMACKey string) string { return fmt.Sprintf(` resource "tfe_organization_run_task" "foobar" { organization = "%s" url = "%s" name = "foobar-task-%d-new" enabled = true - hmac_key = "somepassword" + hmac_key = "%s" description = "a description" } -`, orgName, runTaskURL, rInt) +`, orgName, runTaskURL, rInt, runTaskHMACKey) } diff --git a/internal/provider/resource_tfe_project_policy_set_test.go b/internal/provider/resource_tfe_project_policy_set_test.go index 0648efbc5..88fea72fb 100644 --- a/internal/provider/resource_tfe_project_policy_set_test.go +++ b/internal/provider/resource_tfe_project_policy_set_test.go @@ -16,7 +16,6 @@ import ( ) func TestAccTFEProjectPolicySet_basic(t *testing.T) { - skipUnlessBeta(t) rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int() tfeClient, err := getClientUsingEnv() @@ -58,7 +57,6 @@ func TestAccTFEProjectPolicySet_basic(t *testing.T) { } func TestAccTFEProjectPolicySet_incorrectImportSyntax(t *testing.T) { - skipUnlessBeta(t) rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int() tfeClient, err := getClientUsingEnv() diff --git a/internal/provider/resource_tfe_workspace_policy_set_exclusion_test.go b/internal/provider/resource_tfe_workspace_policy_set_exclusion_test.go index 4a1691b97..143bcb125 100644 --- a/internal/provider/resource_tfe_workspace_policy_set_exclusion_test.go +++ b/internal/provider/resource_tfe_workspace_policy_set_exclusion_test.go @@ -15,7 +15,6 @@ import ( ) func TestAccTFEWorkspacePolicySetExclusion_basic(t *testing.T) { - skipUnlessBeta(t) rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int() tfeClient, err := getClientUsingEnv() @@ -49,7 +48,6 @@ func TestAccTFEWorkspacePolicySetExclusion_basic(t *testing.T) { } func TestAccTFEWorkspacePolicySetExclusion_incorrectImportSyntax(t *testing.T) { - skipUnlessBeta(t) rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int() tfeClient, err := getClientUsingEnv() diff --git a/internal/provider/resource_tfe_workspace_run_task_test.go b/internal/provider/resource_tfe_workspace_run_task_test.go index f65ad1f09..d267c0050 100644 --- a/internal/provider/resource_tfe_workspace_run_task_test.go +++ b/internal/provider/resource_tfe_workspace_run_task_test.go @@ -32,42 +32,6 @@ func TestAccTFEWorkspaceRunTask_create(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccTFEWorkspaceRunTask_basic(org.Name, runTasksURL()), - Check: resource.ComposeTestCheckFunc( - testAccCheckTFEWorkspaceRunTaskExists("tfe_workspace_run_task.foobar", workspaceTask), - resource.TestCheckResourceAttr("tfe_workspace_run_task.foobar", "enforcement_level", "advisory"), - ), - }, - { - Config: testAccTFEWorkspaceRunTask_update(org.Name, runTasksURL()), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("tfe_workspace_run_task.foobar", "enforcement_level", "mandatory"), - ), - }, - }, - }) -} - -func TestAccTFEWorkspaceRunTask_beta_create(t *testing.T) { - skipUnlessRunTasksDefined(t) - skipUnlessBeta(t) - - tfeClient, err := getClientUsingEnv() - if err != nil { - t.Fatal(err) - } - - org, orgCleanup := createBusinessOrganization(t, tfeClient) - t.Cleanup(orgCleanup) - - workspaceTask := &tfe.WorkspaceRunTask{} - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckTFEWorkspaceRunTaskDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTFEWorkspaceRunTask_beta_basic(org.Name, runTasksURL()), Check: resource.ComposeTestCheckFunc( testAccCheckTFEWorkspaceRunTaskExists("tfe_workspace_run_task.foobar", workspaceTask), resource.TestCheckResourceAttr("tfe_workspace_run_task.foobar", "enforcement_level", "advisory"), @@ -75,7 +39,7 @@ func TestAccTFEWorkspaceRunTask_beta_create(t *testing.T) { ), }, { - Config: testAccTFEWorkspaceRunTask_beta_update(org.Name, runTasksURL()), + Config: testAccTFEWorkspaceRunTask_update(org.Name, runTasksURL()), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("tfe_workspace_run_task.foobar", "enforcement_level", "mandatory"), resource.TestCheckResourceAttr("tfe_workspace_run_task.foobar", "stage", "pre_plan"), @@ -186,56 +150,6 @@ resource "tfe_workspace" "foobar" { organization = local.organization_name } -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" -} -`, orgName, runTaskURL) -} - -func testAccTFEWorkspaceRunTask_update(orgName, runTaskURL string) string { - return fmt.Sprintf(` -locals { - organization_name = "%s" -} - -resource "tfe_organization_run_task" "foobar" { - organization = local.organization_name - url = "%s" - name = "foobar-task" -} - -resource "tfe_workspace" "foobar" { - name = "workspace-test" - organization = local.organization_name -} - -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" -} -`, orgName, runTaskURL) -} - -func testAccTFEWorkspaceRunTask_beta_basic(orgName, runTaskURL string) string { - return fmt.Sprintf(` -locals { - organization_name = "%s" -} - -resource "tfe_organization_run_task" "foobar" { - organization = local.organization_name - 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 @@ -245,7 +159,7 @@ resource "tfe_workspace_run_task" "foobar" { `, orgName, runTaskURL) } -func testAccTFEWorkspaceRunTask_beta_update(orgName, runTaskURL string) string { +func testAccTFEWorkspaceRunTask_update(orgName, runTaskURL string) string { return fmt.Sprintf(` locals { organization_name = "%s" diff --git a/internal/provider/subscription_updater_test.go b/internal/provider/subscription_updater_test.go new file mode 100644 index 000000000..50813447e --- /dev/null +++ b/internal/provider/subscription_updater_test.go @@ -0,0 +1,117 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + "fmt" + "net/url" + "testing" + "time" + + tfe "github.com/hashicorp/go-tfe" +) + +type featureSet struct { + ID string `jsonapi:"primary,feature-sets"` +} + +type featureSetList struct { + Items []*featureSet + *tfe.Pagination +} + +type featureSetListOptions struct { + Q string `url:"q,omitempty"` +} + +type updateFeatureSetOptions struct { + Type string `jsonapi:"primary,subscription"` + RunsCeiling *int `jsonapi:"attr,runs-ceiling,omitempty"` + ContractStartAt *time.Time `jsonapi:"attr,contract-start-at,iso8601,omitempty"` + ContractUserLimit *int `jsonapi:"attr,contract-user-limit,omitempty"` + ContractApplyLimit *int `jsonapi:"attr,contract-apply-limit,omitempty"` + + FeatureSet *featureSet `jsonapi:"relation,feature-set"` +} + +type organizationSubscriptionUpdater struct { + organization *tfe.Organization + planName string + updateOpts updateFeatureSetOptions +} + +func newSubscriptionUpdater(organization *tfe.Organization) *organizationSubscriptionUpdater { + return &organizationSubscriptionUpdater{ + organization: organization, + updateOpts: updateFeatureSetOptions{}, + } +} + +func (b *organizationSubscriptionUpdater) WithBusinessPlan() *organizationSubscriptionUpdater { + b.planName = "Business" + + ceiling := 10 + start := time.Now() + userLimit := 1000 + applyLimit := 5000 + + b.updateOpts.RunsCeiling = &ceiling + b.updateOpts.ContractStartAt = &start + b.updateOpts.ContractUserLimit = &userLimit + b.updateOpts.ContractApplyLimit = &applyLimit + return b +} + +func (b *organizationSubscriptionUpdater) WithTrialPlan() *organizationSubscriptionUpdater { + b.planName = "Trial" + ceiling := 1 + b.updateOpts.RunsCeiling = &ceiling + return b +} + +// Attempts to change an organization's subscription to a different plan. Requires a user token with admin access. +func (b *organizationSubscriptionUpdater) Update(t *testing.T) { + if enterpriseEnabled() { + t.Skip("Cannot upgrade an organization's subscription when enterprise is enabled. Set ENABLE_TFE=0 to run.") + } + + if b.planName == "" { + t.Fatal("organizationSubscriptionUpdater requires a plan") + return + } + + adminClient := testAdminClient(t, provisionLicensesAdmin) + req, err := adminClient.NewRequest("GET", "admin/feature-sets", featureSetListOptions{ + Q: b.planName, + }) + if err != nil { + t.Fatal(err) + return + } + + fsl := &featureSetList{} + err = req.Do(context.Background(), fsl) + if err != nil { + t.Fatalf("failed to enumerate feature sets: %v", err) + return + } else if len(fsl.Items) == 0 { + t.Fatalf("feature set response was empty") + return + } + + b.updateOpts.FeatureSet = fsl.Items[0] + + u := fmt.Sprintf("admin/organizations/%s/subscription", url.QueryEscape(b.organization.Name)) + req, err = adminClient.NewRequest("POST", u, &b.updateOpts) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + return + } + + err = req.Do(context.Background(), nil) + if err != nil { + t.Fatalf("Failed to upgrade subscription: %v", err) + } +}