From d1106e9e22f97c8bca3d454317491b8feecf0e1f Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Wed, 22 Apr 2015 12:53:05 -0500 Subject: [PATCH 1/5] helper/resource: add UniqueId() helper A generic function for provider resources to use to get a unique identifier. --- helper/resource/id.go | 25 +++++++++++++++++++++++++ helper/resource/id_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 helper/resource/id.go create mode 100644 helper/resource/id_test.go diff --git a/helper/resource/id.go b/helper/resource/id.go new file mode 100644 index 000000000..3bb68c51d --- /dev/null +++ b/helper/resource/id.go @@ -0,0 +1,25 @@ +package resource + +import ( + "crypto/rand" + "encoding/base32" + "fmt" + "strings" +) + +const UniqueIdPrefix = `terraform-` + +// Helper for a resource to generate a unique identifier +// +// This uses a simple RFC 4122 v4 UUID with some basic cosmetic filters +// applied (remove padding, downcase) to help distinguishing visually between +// identifiers. +func UniqueId() string { + var uuid [16]byte + rand.Read(uuid[:]) + return fmt.Sprintf("%s%s", UniqueIdPrefix, + strings.ToLower( + strings.Replace( + base32.StdEncoding.EncodeToString(uuid[:]), + "=", "", -1))) +} diff --git a/helper/resource/id_test.go b/helper/resource/id_test.go new file mode 100644 index 000000000..245155b7a --- /dev/null +++ b/helper/resource/id_test.go @@ -0,0 +1,25 @@ +package resource + +import ( + "strings" + "testing" +) + +func TestUniqueId(t *testing.T) { + iterations := 10000 + ids := make(map[string]struct{}) + var id string + for i := 0; i < iterations; i++ { + id = UniqueId() + + if _, ok := ids[id]; ok { + t.Fatalf("Got duplicated id! %s", id) + } + + if !strings.HasPrefix(id, "terraform-") { + t.Fatalf("Unique ID didn't have terraform- prefix! %s", id) + } + + ids[id] = struct{}{} + } +} From 94f703692cae44140701e855edd3b8a252cd2c44 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Wed, 22 Apr 2015 12:53:47 -0500 Subject: [PATCH 2/5] provider/aws: switch to helper for LC names --- .../aws/resource_aws_launch_configuration.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/builtin/providers/aws/resource_aws_launch_configuration.go b/builtin/providers/aws/resource_aws_launch_configuration.go index 3ed551c86..8d8e09342 100644 --- a/builtin/providers/aws/resource_aws_launch_configuration.go +++ b/builtin/providers/aws/resource_aws_launch_configuration.go @@ -364,16 +364,13 @@ func resourceAwsLaunchConfigurationCreate(d *schema.ResourceData, meta interface createLaunchConfigurationOpts.BlockDeviceMappings = blockDevices } - var id string + var lcName string if v, ok := d.GetOk("name"); ok { - id = v.(string) + lcName = v.(string) } else { - hash := sha1.Sum([]byte(fmt.Sprintf("%#v", createLaunchConfigurationOpts))) - configName := fmt.Sprintf("terraform-%s", base64.URLEncoding.EncodeToString(hash[:])) - log.Printf("[DEBUG] Computed Launch config name: %s", configName) - id = configName + lcName = resource.UniqueId() } - createLaunchConfigurationOpts.LaunchConfigurationName = aws.String(id) + createLaunchConfigurationOpts.LaunchConfigurationName = aws.String(lcName) log.Printf( "[DEBUG] autoscaling create launch configuration: %#v", createLaunchConfigurationOpts) @@ -382,7 +379,7 @@ func resourceAwsLaunchConfigurationCreate(d *schema.ResourceData, meta interface return fmt.Errorf("Error creating launch configuration: %s", err) } - d.SetId(id) + d.SetId(lcName) log.Printf("[INFO] launch configuration ID: %s", d.Id()) // We put a Retry here since sometimes eventual consistency bites From 33de319293eab5150daad5d8272d8f477dd49d3f Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Wed, 22 Apr 2015 12:56:06 -0500 Subject: [PATCH 3/5] provider/aws: allow SG names to be generated --- .../aws/resource_aws_security_group.go | 15 +++++-- .../aws/resource_aws_security_group_test.go | 45 +++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/builtin/providers/aws/resource_aws_security_group.go b/builtin/providers/aws/resource_aws_security_group.go index 6b9fdeff3..8a76d316f 100644 --- a/builtin/providers/aws/resource_aws_security_group.go +++ b/builtin/providers/aws/resource_aws_security_group.go @@ -24,7 +24,8 @@ func resourceAwsSecurityGroup() *schema.Resource { Schema: map[string]*schema.Schema{ "name": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, + Computed: true, ForceNew: true, }, @@ -144,9 +145,7 @@ func resourceAwsSecurityGroup() *schema.Resource { func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn - securityGroupOpts := &ec2.CreateSecurityGroupInput{ - GroupName: aws.String(d.Get("name").(string)), - } + securityGroupOpts := &ec2.CreateSecurityGroupInput{} if v := d.Get("vpc_id"); v != nil { securityGroupOpts.VPCID = aws.String(v.(string)) @@ -156,6 +155,14 @@ func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) er securityGroupOpts.Description = aws.String(v.(string)) } + var groupName string + if v, ok := d.GetOk("name"); ok { + groupName = v.(string) + } else { + groupName = resource.UniqueId() + } + securityGroupOpts.GroupName = aws.String(groupName) + log.Printf( "[DEBUG] Security Group create configuration: %#v", securityGroupOpts) createResp, err := conn.CreateSecurityGroup(securityGroupOpts) diff --git a/builtin/providers/aws/resource_aws_security_group_test.go b/builtin/providers/aws/resource_aws_security_group_test.go index 4dd31d099..b4ce116b9 100644 --- a/builtin/providers/aws/resource_aws_security_group_test.go +++ b/builtin/providers/aws/resource_aws_security_group_test.go @@ -3,6 +3,7 @@ package aws import ( "fmt" "reflect" + "strings" "testing" "github.com/awslabs/aws-sdk-go/aws" @@ -184,6 +185,33 @@ func TestAccAWSSecurityGroup_Change(t *testing.T) { }) } +func TestAccAWSSecurityGroup_generatedName(t *testing.T) { + var group ec2.SecurityGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSecurityGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSSecurityGroupConfig_generatedName, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSecurityGroupExists("aws_security_group.web", &group), + func(s *terraform.State) error { + if group.GroupName == nil { + return fmt.Errorf("bad: No SG name") + } + if !strings.HasPrefix(*group.GroupName, "terraform-") { + return fmt.Errorf("No terraform- prefix: %s", *group.GroupName) + } + return nil + }, + ), + }, + }, + }) +} + func testAccCheckAWSSecurityGroupDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn @@ -518,3 +546,20 @@ resource "aws_security_group" "foo" { } } ` + +const testAccAWSSecurityGroupConfig_generatedName = ` +resource "aws_security_group" "web" { + description = "Used in the terraform acceptance tests" + + ingress { + protocol = "tcp" + from_port = 80 + to_port = 8000 + cidr_blocks = ["10.0.0.0/8"] + } + + tags { + Name = "tf-acc-test" + } +} +` From 92ebb60293e985b81fa44de409ec950aa3213fbe Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Wed, 22 Apr 2015 13:16:44 -0500 Subject: [PATCH 4/5] helper/resource: ok let's actually use RFC4122 --- helper/resource/id.go | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/helper/resource/id.go b/helper/resource/id.go index 3bb68c51d..b3af422fc 100644 --- a/helper/resource/id.go +++ b/helper/resource/id.go @@ -12,14 +12,30 @@ const UniqueIdPrefix = `terraform-` // Helper for a resource to generate a unique identifier // // This uses a simple RFC 4122 v4 UUID with some basic cosmetic filters -// applied (remove padding, downcase) to help distinguishing visually between -// identifiers. +// applied (base32, remove padding, downcase) to make visually distinguishing +// identifiers easier. func UniqueId() string { - var uuid [16]byte - rand.Read(uuid[:]) return fmt.Sprintf("%s%s", UniqueIdPrefix, strings.ToLower( strings.Replace( - base32.StdEncoding.EncodeToString(uuid[:]), + base32.StdEncoding.EncodeToString(uuidV4()), "=", "", -1))) } + +func uuidV4() []byte { + var uuid [16]byte + + // Set all the other bits to randomly (or pseudo-randomly) chosen + // values. + rand.Read(uuid[:]) + + // Set the two most significant bits (bits 6 and 7) of the + // clock_seq_hi_and_reserved to zero and one, respectively. + uuid[8] = (uuid[8] | 0x80) & 0x8f + + // Set the four most significant bits (bits 12 through 15) of the + // time_hi_and_version field to the 4-bit version number from Section 4.1.3. + uuid[6] = (uuid[6] | 0x40) & 0x4f + + return uuid[:] +} From 079856620a79fea1c01a432be53a9e4adb3437e3 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Wed, 22 Apr 2015 13:27:20 -0500 Subject: [PATCH 5/5] provider/aws: set default SG description because requiring a SG description is annoying --- builtin/providers/aws/resource_aws_security_group.go | 3 ++- builtin/providers/aws/resource_aws_security_group_test.go | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/builtin/providers/aws/resource_aws_security_group.go b/builtin/providers/aws/resource_aws_security_group.go index 8a76d316f..044d0afaa 100644 --- a/builtin/providers/aws/resource_aws_security_group.go +++ b/builtin/providers/aws/resource_aws_security_group.go @@ -31,7 +31,8 @@ func resourceAwsSecurityGroup() *schema.Resource { "description": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, + Default: "Managed by Terraform", }, "vpc_id": &schema.Schema{ diff --git a/builtin/providers/aws/resource_aws_security_group_test.go b/builtin/providers/aws/resource_aws_security_group_test.go index b4ce116b9..6d162ae79 100644 --- a/builtin/providers/aws/resource_aws_security_group_test.go +++ b/builtin/providers/aws/resource_aws_security_group_test.go @@ -197,6 +197,8 @@ func TestAccAWSSecurityGroup_generatedName(t *testing.T) { Config: testAccAWSSecurityGroupConfig_generatedName, Check: resource.ComposeTestCheckFunc( testAccCheckAWSSecurityGroupExists("aws_security_group.web", &group), + resource.TestCheckResourceAttr( + "aws_security_group.web", "description", "Managed by Terraform"), func(s *terraform.State) error { if group.GroupName == nil { return fmt.Errorf("bad: No SG name") @@ -549,8 +551,6 @@ resource "aws_security_group" "foo" { const testAccAWSSecurityGroupConfig_generatedName = ` resource "aws_security_group" "web" { - description = "Used in the terraform acceptance tests" - ingress { protocol = "tcp" from_port = 80