From f25d34f094521fd44fab967a8d8d3fd9e4616762 Mon Sep 17 00:00:00 2001 From: Sebastian Rivera Date: Fri, 17 Dec 2021 15:29:50 -0500 Subject: [PATCH 1/3] Added Tfe Terraform Version resource This commit implements the Terraform version resource block "tfe_terraform_version" which allows practitioners to create and manage Terraform versions on TFE. This commit also outlines two tests for this resource. It generates a mock terraform version using a random version number and SHA. The URL attribute points to hashicorp.com since the TFC API doesn't download or verify that the zip exists when the resource is created. The basic test tests against the required parameters and the full test includes the optional parameters. --- tfe/provider.go | 1 + tfe/resource_tfe_terraform_version.go | 136 +++++++++++++ tfe/resource_tfe_terraform_version_test.go | 215 +++++++++++++++++++++ 3 files changed, 352 insertions(+) create mode 100644 tfe/resource_tfe_terraform_version.go create mode 100644 tfe/resource_tfe_terraform_version_test.go diff --git a/tfe/provider.go b/tfe/provider.go index c147b8cc8..56aae2795 100644 --- a/tfe/provider.go +++ b/tfe/provider.go @@ -112,6 +112,7 @@ func Provider() *schema.Provider { "tfe_team_member": resourceTFETeamMember(), "tfe_team_members": resourceTFETeamMembers(), "tfe_team_token": resourceTFETeamToken(), + "tfe_terraform_version": resourceTFETerraformVersion(), "tfe_workspace": resourceTFEWorkspace(), "tfe_variable": resourceTFEVariable(), }, diff --git a/tfe/resource_tfe_terraform_version.go b/tfe/resource_tfe_terraform_version.go new file mode 100644 index 000000000..d67b70766 --- /dev/null +++ b/tfe/resource_tfe_terraform_version.go @@ -0,0 +1,136 @@ +package tfe + +import ( + "fmt" + "log" + + tfe "github.com/hashicorp/go-tfe" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceTFETerraformVersion() *schema.Resource { + return &schema.Resource{ + Create: resourceTFETerraformVersionCreate, + Read: resourceTFETerraformVersionRead, + Update: resourceTFETerraformVersionUpdate, + Delete: resourceTFETerraformVersionDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "version": { + Type: schema.TypeString, + Required: true, + }, + "url": { + Type: schema.TypeString, + Required: true, + }, + "sha": { + Type: schema.TypeString, + Required: true, + }, + "official": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "beta": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + }, + } +} + +func resourceTFETerraformVersionCreate(d *schema.ResourceData, meta interface{}) error { + tfeClient := meta.(*tfe.Client) + + opts := tfe.AdminTerraformVersionCreateOptions{ + Version: tfe.String(d.Get("version").(string)), + URL: tfe.String(d.Get("url").(string)), + Sha: tfe.String(d.Get("sha").(string)), + Official: tfe.Bool(d.Get("official").(bool)), + Enabled: tfe.Bool(d.Get("enabled").(bool)), + Beta: tfe.Bool(d.Get("beta").(bool)), + } + + log.Printf("[DEBUG] Create new Terraform version: %s", *opts.Version) + v, err := tfeClient.Admin.TerraformVersions.Create(ctx, opts) + if err != nil { + return fmt.Errorf("Error creating the new Terraform version %s: %v", *opts.Version, err) + } + + d.SetId(v.ID) + + return resourceTFETerraformVersionUpdate(d, meta) +} + +func resourceTFETerraformVersionRead(d *schema.ResourceData, meta interface{}) error { + tfeClient := meta.(*tfe.Client) + + log.Printf("[DEBUG] Read configuration of Terraform version: %s", d.Id()) + v, err := tfeClient.Admin.TerraformVersions.Read(ctx, d.Id()) + if err != nil { + if err == tfe.ErrResourceNotFound { + log.Printf("[DEBUG] Terraform version %s does no longer exist", d.Id()) + d.SetId("") + return nil + } + return err + } + + d.Set("version", v.Version) + d.Set("url", v.URL) + d.Set("sha", v.Sha) + d.Set("official", v.Official) + d.Set("enabled", v.Enabled) + d.Set("beta", v.Beta) + + return nil +} + +func resourceTFETerraformVersionUpdate(d *schema.ResourceData, meta interface{}) error { + tfeClient := meta.(*tfe.Client) + + opts := tfe.AdminTerraformVersionUpdateOptions{ + Version: tfe.String(d.Get("version").(string)), + URL: tfe.String(d.Get("url").(string)), + Sha: tfe.String(d.Get("sha").(string)), + Official: tfe.Bool(d.Get("official").(bool)), + Enabled: tfe.Bool(d.Get("enabled").(bool)), + Beta: tfe.Bool(d.Get("beta").(bool)), + } + + log.Printf("[DEBUG] Update configuration of Terraform version: %s", d.Id()) + v, err := tfeClient.Admin.TerraformVersions.Update(ctx, d.Id(), opts) + if err != nil { + return fmt.Errorf("Error updating Terraform version %s: %v", d.Id(), err) + } + + d.SetId(v.ID) + + return resourceTFETerraformVersionRead(d, meta) +} + +func resourceTFETerraformVersionDelete(d *schema.ResourceData, meta interface{}) error { + tfeClient := meta.(*tfe.Client) + + log.Printf("[DEBUG] Delete Terraform version: %s", d.Id()) + err := tfeClient.Admin.TerraformVersions.Delete(ctx, d.Id()) + if err != nil { + if err == tfe.ErrResourceNotFound { + return nil + } + return fmt.Errorf("Error deleting Terraform version %s: %v", d.Id(), err) + } + + return nil +} diff --git a/tfe/resource_tfe_terraform_version_test.go b/tfe/resource_tfe_terraform_version_test.go new file mode 100644 index 000000000..1e78d6206 --- /dev/null +++ b/tfe/resource_tfe_terraform_version_test.go @@ -0,0 +1,215 @@ +package tfe + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "fmt" + "math/rand" + "testing" + "time" + + tfe "github.com/hashicorp/go-tfe" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccTFETerraformVersion_basic(t *testing.T) { + skipIfFreeOnly(t) + + tfVersion := &tfe.AdminTerraformVersion{} + sha := genSha(t, "secret", "data") + rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int() + version := genVersion(rInt) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckTFETerraformVersionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTFETerraformVersion_basic(version, sha), + Check: resource.ComposeTestCheckFunc( + testAccCheckTFETerraformVersionExists("tfe_terraform_version.foobar", tfVersion), + testAccCheckTFETerraformVersionAttributesBasic(tfVersion, version, sha), + resource.TestCheckResourceAttr( + "tfe_terraform_version.foobar", "version", version), + resource.TestCheckResourceAttr( + "tfe_terraform_version.foobar", "url", "https://www.hashicorp.com"), + resource.TestCheckResourceAttr( + "tfe_terraform_version.foobar", "sha", sha), + ), + }, + }, + }) +} + +func TestAccTFETerraformVersion_full(t *testing.T) { + skipIfFreeOnly(t) + + tfVersion := &tfe.AdminTerraformVersion{} + sha := genSha(t, "secret", "data") + rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int() + version := genVersion(rInt) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckTFETerraformVersionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTFETerraformVersion_full(version, sha), + Check: resource.ComposeTestCheckFunc( + testAccCheckTFETerraformVersionExists("tfe_terraform_version.foobar", tfVersion), + testAccCheckTFETerraformVersionAttributesFull(tfVersion, version, sha), + resource.TestCheckResourceAttr( + "tfe_terraform_version.foobar", "version", version), + resource.TestCheckResourceAttr( + "tfe_terraform_version.foobar", "url", "https://www.hashicorp.com"), + resource.TestCheckResourceAttr( + "tfe_terraform_version.foobar", "sha", sha), + resource.TestCheckResourceAttr( + "tfe_terraform_version.foobar", "official", "false"), + resource.TestCheckResourceAttr( + "tfe_terraform_version.foobar", "enabled", "true"), + resource.TestCheckResourceAttr( + "tfe_terraform_version.foobar", "beta", "true"), + ), + }, + }, + }) +} + +func testAccCheckTFETerraformVersionDestroy(s *terraform.State) error { + tfeClient := testAccProvider.Meta().(*tfe.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "tfe_terraform_version" { + continue + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No instance ID is set") + } + + _, err := tfeClient.Admin.TerraformVersions.Read(ctx, rs.Primary.ID) + if err == nil { + return fmt.Errorf("Terraform version %s still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckTFETerraformVersionExists(n string, tfVersion *tfe.AdminTerraformVersion) resource.TestCheckFunc { + return func(s *terraform.State) error { + tfeClient := testAccProvider.Meta().(*tfe.Client) + + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No instance ID is set") + } + + v, err := tfeClient.Admin.TerraformVersions.Read(ctx, rs.Primary.ID) + if err != nil { + return err + } + + if v.ID != rs.Primary.ID { + return fmt.Errorf("Terraform version not found") + } + + *tfVersion = *v + + return nil + } +} + +func testAccCheckTFETerraformVersionAttributesBasic(tfVersion *tfe.AdminTerraformVersion, version string, sha string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if tfVersion.URL != "https://www.hashicorp.com" { + return fmt.Errorf("Bad URL: %s", tfVersion.URL) + } + + if tfVersion.Version != version { + return fmt.Errorf("Bad version: %s", tfVersion.Version) + } + + if tfVersion.Sha != sha { + return fmt.Errorf("Bad value for Sha: %v", tfVersion.Sha) + } + + return nil + } +} + +func testAccCheckTFETerraformVersionAttributesFull(tfVersion *tfe.AdminTerraformVersion, version string, sha string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if tfVersion.URL != "https://www.hashicorp.com" { + return fmt.Errorf("Bad URL: %s", tfVersion.URL) + } + + if tfVersion.Version != version { + return fmt.Errorf("Bad version: %s", tfVersion.Version) + } + + if tfVersion.Sha != sha { + return fmt.Errorf("Bad value for Sha: %v", tfVersion.Sha) + } + + if tfVersion.Official != false { + return fmt.Errorf("Bad value for official: %t", tfVersion.Official) + } + + if tfVersion.Enabled != true { + return fmt.Errorf("Bad value for enabled: %t", tfVersion.Enabled) + } + + if tfVersion.Beta != true { + return fmt.Errorf("Bad value for beta: %t", tfVersion.Beta) + } + + return nil + } +} + +func testAccTFETerraformVersion_basic(version string, sha string) string { + return fmt.Sprintf(` +resource "tfe_terraform_version" "foobar" { + version = "%s" + url = "https://www.hashicorp.com" + sha = "%s" +}`, version, sha) +} + +func testAccTFETerraformVersion_full(version string, sha string) string { + return fmt.Sprintf(` +resource "tfe_terraform_version" "foobar" { + version = "%s" + url = "https://www.hashicorp.com" + sha = "%s" + official = false + enabled = true + beta = true +}`, version, sha) +} + +// Helper functions +func genSha(t *testing.T, secret, data string) string { + h := hmac.New(sha256.New, []byte(secret)) + _, err := h.Write([]byte(data)) + if err != nil { + t.Fatalf("error writing hmac: %s", err) + } + + sha := hex.EncodeToString(h.Sum(nil)) + return sha +} + +func genVersion(rInt int) string { + return fmt.Sprintf("%d.%d.%d", rInt, rInt, rInt) +} From 10ede73d3441a1fb3be9121e5388c53c25afa085 Mon Sep 17 00:00:00 2001 From: Sebastian Rivera Date: Tue, 4 Jan 2022 16:50:11 -0500 Subject: [PATCH 2/3] Added website docs --- .../docs/r/terraform_version.html.markdown | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 website/docs/r/terraform_version.html.markdown diff --git a/website/docs/r/terraform_version.html.markdown b/website/docs/r/terraform_version.html.markdown new file mode 100644 index 000000000..cc3dce198 --- /dev/null +++ b/website/docs/r/terraform_version.html.markdown @@ -0,0 +1,46 @@ +--- +layout: "tfe" +page_title: "Terraform Enterprise: tfe_terraform_version" +sidebar_current: "docs-resource-tfe-terraform-version-x" +description: |- + Manages Terraform versions +--- + +# tfe_terraform_version" + +Manages Terraform versions. + +## Example Usage + +Basic Usage: + +```hcl +resource "tfe_terraform_version" "test" { + version = "99.99.99" + url = "https://tfe-host.com/path/to/terraform.zip" + sha = "e75ac73deb69a6b3aa667cb0b8b731aee79e2904" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `version` - (Required) A semantic version string in N.N.N or N.N.N-bundleName format. +* `url` - (Required) The URL where a ZIP-compressed 64-bit Linux binary of this version can be downloaded. +* `sha` - (Required) The SHA-256 checksum of the compressed Terraform binary. +* `official` - (Optional) Whether or not this is an official release of Terraform. Defaults to "false". +* `enabled` - (Optional) Whether or not this version of Terraform is enabled for use in Terraform Cloud. Defaults to "true". +* `beta` - (Optional) Whether or not this version of Terraform is beta pre-release. Defaults to "false". + +## Attributes Reference + +* `id` The ID of the Terraform version + +## Import + +Terraform versions can be imported; use `` as the import ID. For example: + +```shell +terraform import tfe_terraform_version.test tool-L4oe7rNwn7J4E5Yr +``` \ No newline at end of file From 6a9e4ed0ee8099ad424e9872f149ff44a8060248 Mon Sep 17 00:00:00 2001 From: Sebastian Rivera Date: Wed, 5 Jan 2022 10:18:37 -0500 Subject: [PATCH 3/3] Minor fixes to Terraform version docs --- website/docs/r/terraform_version.html.markdown | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/website/docs/r/terraform_version.html.markdown b/website/docs/r/terraform_version.html.markdown index cc3dce198..cb2341a0f 100644 --- a/website/docs/r/terraform_version.html.markdown +++ b/website/docs/r/terraform_version.html.markdown @@ -6,9 +6,9 @@ description: |- Manages Terraform versions --- -# tfe_terraform_version" +# tfe_terraform_version -Manages Terraform versions. +Manage Terraform versions available on Terraform Cloud/Enterprise. ## Example Usage @@ -16,7 +16,7 @@ Basic Usage: ```hcl resource "tfe_terraform_version" "test" { - version = "99.99.99" + version = "1.1.2-custom" url = "https://tfe-host.com/path/to/terraform.zip" sha = "e75ac73deb69a6b3aa667cb0b8b731aee79e2904" } @@ -30,7 +30,7 @@ The following arguments are supported: * `url` - (Required) The URL where a ZIP-compressed 64-bit Linux binary of this version can be downloaded. * `sha` - (Required) The SHA-256 checksum of the compressed Terraform binary. * `official` - (Optional) Whether or not this is an official release of Terraform. Defaults to "false". -* `enabled` - (Optional) Whether or not this version of Terraform is enabled for use in Terraform Cloud. Defaults to "true". +* `enabled` - (Optional) Whether or not this version of Terraform is enabled for use in Terraform Cloud/Enterprise. Defaults to "true". * `beta` - (Optional) Whether or not this version of Terraform is beta pre-release. Defaults to "false". ## Attributes Reference @@ -43,4 +43,6 @@ Terraform versions can be imported; use `` as the import I ```shell terraform import tfe_terraform_version.test tool-L4oe7rNwn7J4E5Yr -``` \ No newline at end of file +``` + +-> **Note:** You can fetch a Terraform version ID from the URL of an exisiting version in the Terraform Cloud UI. The ID is in the format `tool-`