Skip to content

Commit

Permalink
Added Tfe Terraform Version resource
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sebasslash committed Jan 4, 2022
1 parent 72f4d0e commit f25d34f
Show file tree
Hide file tree
Showing 3 changed files with 352 additions and 0 deletions.
1 change: 1 addition & 0 deletions tfe/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
},
Expand Down
136 changes: 136 additions & 0 deletions tfe/resource_tfe_terraform_version.go
Original file line number Diff line number Diff line change
@@ -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
}
215 changes: 215 additions & 0 deletions tfe/resource_tfe_terraform_version_test.go
Original file line number Diff line number Diff line change
@@ -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)
}

0 comments on commit f25d34f

Please sign in to comment.