diff --git a/builtin/providers/aws/config.go b/builtin/providers/aws/config.go index 9c6599258..e38297947 100644 --- a/builtin/providers/aws/config.go +++ b/builtin/providers/aws/config.go @@ -10,6 +10,7 @@ import ( "github.com/awslabs/aws-sdk-go/aws" "github.com/awslabs/aws-sdk-go/service/autoscaling" "github.com/awslabs/aws-sdk-go/service/ec2" + "github.com/awslabs/aws-sdk-go/service/elasticache" "github.com/awslabs/aws-sdk-go/service/elb" "github.com/awslabs/aws-sdk-go/service/iam" "github.com/awslabs/aws-sdk-go/service/rds" @@ -36,6 +37,7 @@ type AWSClient struct { region string rdsconn *rds.RDS iamconn *iam.IAM + elasticacheconn *elasticache.ElastiCache } // Client configures and returns a fully initailized AWSClient @@ -96,6 +98,8 @@ func (c *Config) Client() (interface{}, error) { Region: "us-east-1", }) + log.Println("[INFO] Initializing Elasticache Connection") + client.elasticacheconn = elasticache.New(awsConfig) } if len(errs) > 0 { diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 3e534c4b6..6e1a0e0b9 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -80,6 +80,9 @@ func Provider() terraform.ResourceProvider { "aws_db_parameter_group": resourceAwsDbParameterGroup(), "aws_db_security_group": resourceAwsDbSecurityGroup(), "aws_db_subnet_group": resourceAwsDbSubnetGroup(), + "aws_elasticache": resourceAwsElasticache(), + "aws_elasticache_subnet_group": resourceAwsElasticacheSubnetGroup(), + "aws_elasticache_security_group": resourceAwsElasticacheSecurityGroup(), "aws_eip": resourceAwsEip(), "aws_elb": resourceAwsElb(), "aws_instance": resourceAwsInstance(), diff --git a/builtin/providers/aws/resource_aws_elasticache.go b/builtin/providers/aws/resource_aws_elasticache.go new file mode 100644 index 000000000..6d691c91a --- /dev/null +++ b/builtin/providers/aws/resource_aws_elasticache.go @@ -0,0 +1,240 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/elasticache" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsElasticache() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsElasticacheCreate, + Read: resourceAwsElasticacheRead, + Delete: resourceAwsElasticacheDelete, + + Schema: map[string]*schema.Schema{ + "cluster_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "engine": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "node_type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "num_cache_nodes": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "parameter_group_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "port": &schema.Schema{ + Type: schema.TypeInt, + Default: 11211, + Optional: true, + ForceNew: true, + }, + "engine_version": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "subnet_group_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "security_group_names": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: func(v interface{}) int { + return hashcode.String(v.(string)) + }, + }, + "security_group_ids": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: func(v interface{}) int { + return hashcode.String(v.(string)) + }, + }, + }, + } +} + +func resourceAwsElasticacheCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).elasticacheconn + + clusterId := d.Get("cluster_id").(string) + nodeType := d.Get("node_type").(string) // e.g) cache.m1.small + numNodes := int64(d.Get("num_cache_nodes").(int)) // 2 + engine := d.Get("engine").(string) // memcached + engineVersion := d.Get("engine_version").(string) // 1.4.14 + port := int64(d.Get("port").(int)) // 11211 + subnetGroupName := d.Get("subnet_group_name").(string) + securityNameSet := d.Get("security_group_names").(*schema.Set) + securityIdSet := d.Get("security_group_ids").(*schema.Set) + paramGroupName := d.Get("parameter_group_name").(string) // default.memcached1.4 + + securityNames := expandStringList(securityNameSet.List()) + securityIds := expandStringList(securityIdSet.List()) + + req := &elasticache.CreateCacheClusterInput{ + CacheClusterID: aws.String(clusterId), + CacheNodeType: aws.String(nodeType), + NumCacheNodes: aws.Long(numNodes), + Engine: aws.String(engine), + EngineVersion: aws.String(engineVersion), + Port: aws.Long(port), + CacheSubnetGroupName: aws.String(subnetGroupName), + CacheSecurityGroupNames: securityNames, + SecurityGroupIDs: securityIds, + CacheParameterGroupName: aws.String(paramGroupName), + } + + _, err := conn.CreateCacheCluster(req) + if err != nil { + return fmt.Errorf("Error creating Elasticache: %s", err) + } + + pending := []string{"creating"} + stateConf := &resource.StateChangeConf{ + Pending: pending, + Target: "available", + Refresh: CacheClusterStateRefreshFunc(conn, d.Id(), "available", pending), + Timeout: 10 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + log.Printf("[DEBUG] Waiting for state to become available: %v", d.Id()) + _, sterr := stateConf.WaitForState() + if sterr != nil { + return fmt.Errorf("Error waiting for elasticache (%s) to be created: %s", d.Id(), sterr) + } + + d.SetId(clusterId) + + return nil +} + +func resourceAwsElasticacheRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).elasticacheconn + req := &elasticache.DescribeCacheClustersInput{ + CacheClusterID: aws.String(d.Id()), + } + + res, err := conn.DescribeCacheClusters(req) + if err != nil { + return err + } + + if len(res.CacheClusters) == 1 { + c := res.CacheClusters[0] + d.Set("cluster_id", c.CacheClusterID) + d.Set("node_type", c.CacheNodeType) + d.Set("num_cache_nodes", c.NumCacheNodes) + d.Set("engine", c.Engine) + d.Set("engine_version", c.EngineVersion) + if c.ConfigurationEndpoint != nil { + d.Set("port", c.ConfigurationEndpoint.Port) + } + d.Set("subnet_group_name", c.CacheSubnetGroupName) + d.Set("security_group_names", c.CacheSecurityGroups) + d.Set("security_group_ids", c.SecurityGroups) + d.Set("parameter_group_name", c.CacheParameterGroup) + } + + return nil +} + +func resourceAwsElasticacheDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).elasticacheconn + + req := &elasticache.DeleteCacheClusterInput{ + CacheClusterID: aws.String(d.Id()), + } + _, err := conn.DeleteCacheCluster(req) + if err != nil { + return err + } + + log.Printf("[DEBUG] Waiting for deletion: %v", d.Id()) + stateConf := &resource.StateChangeConf{ + Pending: []string{"creating", "available", "deleting", "incompatible-parameters", "incompatible-network", "restore-failed"}, + Target: "", + Refresh: CacheClusterStateRefreshFunc(conn, d.Id(), "", []string{}), + Timeout: 10 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, sterr := stateConf.WaitForState() + if sterr != nil { + return fmt.Errorf("Error waiting for elasticache (%s) to delete: %s", d.Id(), sterr) + } + + d.SetId("") + + return nil +} + +func CacheClusterStateRefreshFunc(conn *elasticache.ElastiCache, clusterID, givenState string, pending []string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + resp, err := conn.DescribeCacheClusters(&elasticache.DescribeCacheClustersInput{ + CacheClusterID: aws.String(clusterID), + }) + if err != nil { + apierr := err.(aws.APIError) + log.Printf("[DEBUG] message: %v, code: %v", apierr.Message, apierr.Code) + if apierr.Message == fmt.Sprintf("CacheCluster not found: %v", clusterID) { + log.Printf("[DEBUG] Detect deletion") + return nil, "", nil + } + + log.Printf("[ERROR] CacheClusterStateRefreshFunc: %s", err) + return nil, "", err + } + + c := resp.CacheClusters[0] + log.Printf("[DEBUG] status: %v", *c.CacheClusterStatus) + + // return the current state if it's in the pending array + for _, p := range pending { + s := *c.CacheClusterStatus + if p == s { + log.Printf("[DEBUG] Return with status: %v", *c.CacheClusterStatus) + return c, p, nil + } + } + + // return given state if it's not in pending + if givenState != "" { + return c, givenState, nil + } + log.Printf("[DEBUG] current status: %v", *c.CacheClusterStatus) + return c, *c.CacheClusterStatus, nil + } +} diff --git a/builtin/providers/aws/resource_aws_elasticache_security_group.go b/builtin/providers/aws/resource_aws_elasticache_security_group.go new file mode 100644 index 000000000..d9ce60198 --- /dev/null +++ b/builtin/providers/aws/resource_aws_elasticache_security_group.go @@ -0,0 +1,145 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/elasticache" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsElasticacheSecurityGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsElasticacheSecurityGroupCreate, + Read: resourceAwsElasticacheSecurityGroupRead, + Delete: resourceAwsElasticacheSecurityGroupDelete, + + Schema: map[string]*schema.Schema{ + "description": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "security_group_names": &schema.Schema{ + Type: schema.TypeSet, + Required: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: func(v interface{}) int { + return hashcode.String(v.(string)) + }, + }, + }, + } +} + +func resourceAwsElasticacheSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).elasticacheconn + + name := d.Get("name").(string) + desc := d.Get("description").(string) + nameSet := d.Get("security_group_names").(*schema.Set) + + names := make([]string, nameSet.Len()) + for i, name := range nameSet.List() { + names[i] = name.(string) + } + + log.Printf("[DEBUG] Cache security group create: name: %s, description: %s, security_group_names: %v", name, desc, names) + res, err := conn.CreateCacheSecurityGroup(&elasticache.CreateCacheSecurityGroupInput{ + Description: aws.String(desc), + CacheSecurityGroupName: aws.String(name), + }) + if err != nil { + return fmt.Errorf("Error creating CacheSecurityGroup: %s", err) + } + + for _, n := range names { + log.Printf("[DEBUG] Authorize cache security group ingress name: %v, ec2 security group name: %v", name, n) + _, err = conn.AuthorizeCacheSecurityGroupIngress(&elasticache.AuthorizeCacheSecurityGroupIngressInput{ + CacheSecurityGroupName: aws.String(name), + EC2SecurityGroupName: aws.String(n), + EC2SecurityGroupOwnerID: aws.String(*res.CacheSecurityGroup.OwnerID), + }) + if err != nil { + log.Printf("[ERROR] Failed to authorize: %v", err) + _, err := conn.DeleteCacheSecurityGroup(&elasticache.DeleteCacheSecurityGroupInput{ + CacheSecurityGroupName: aws.String(d.Id()), + }) + log.Printf("[ERROR] Revert cache security group: %v", err) + } + } + + d.SetId(name) + + return nil +} + +func resourceAwsElasticacheSecurityGroupRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).elasticacheconn + req := &elasticache.DescribeCacheSecurityGroupsInput{ + CacheSecurityGroupName: aws.String(d.Get("name").(string)), + } + + res, err := conn.DescribeCacheSecurityGroups(req) + if err != nil { + return err + } + if len(res.CacheSecurityGroups) == 0 { + return fmt.Errorf("Error missing %v", d.Get("name")) + } + + var group *elasticache.CacheSecurityGroup + for _, g := range res.CacheSecurityGroups { + log.Printf("[DEBUG] CacheSecurityGroupName: %v, id: %v", g.CacheSecurityGroupName, d.Id()) + if *g.CacheSecurityGroupName == d.Id() { + group = g + } + } + if group == nil { + return fmt.Errorf("Error retrieving cache security group: %v", res) + } + + d.Set("name", group.CacheSecurityGroupName) + d.Set("description", group.Description) + + return nil +} + +func resourceAwsElasticacheSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).elasticacheconn + + log.Printf("[DEBUG] Cache security group delete: %s", d.Id()) + + return resource.Retry(5*time.Minute, func() error { + _, err := conn.DeleteCacheSecurityGroup(&elasticache.DeleteCacheSecurityGroupInput{ + CacheSecurityGroupName: aws.String(d.Id()), + }) + if err != nil { + apierr, ok := err.(aws.APIError) + if !ok { + return err + } + log.Printf("[DEBUG] APIError.Code: %v", apierr.Code) + switch apierr.Code { + case "InvalidCacheSecurityGroupState": + return err + case "DependencyViolation": + // If it is a dependency violation, we want to retry + return err + default: + return resource.RetryError{Err: err} + } + } + return nil + }) +} diff --git a/builtin/providers/aws/resource_aws_elasticache_security_group_test.go b/builtin/providers/aws/resource_aws_elasticache_security_group_test.go new file mode 100644 index 000000000..a044d4c44 --- /dev/null +++ b/builtin/providers/aws/resource_aws_elasticache_security_group_test.go @@ -0,0 +1,88 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/elasticache" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSEcacheSecurityGroup(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEcacheSecurityGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSEcacheSecurityGroupConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcacheSecurityGroupExists("aws_elasticache_security_group.bar"), + ), + }, + }, + }) +} + +func testAccCheckAWSEcacheSecurityGroupDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).elasticacheconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_elasticache_security_group" { + continue + } + res, err := conn.DescribeCacheSecurityGroups(&elasticache.DescribeCacheSecurityGroupsInput{ + CacheSecurityGroupName: aws.String(rs.Primary.ID), + }) + if err != nil { + return err + } + if len(res.CacheSecurityGroups) > 0 { + return fmt.Errorf("still exist.") + } + } + return nil +} + +func testAccCheckAWSEcacheSecurityGroupExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No cache security group ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).elasticacheconn + _, err := conn.DescribeCacheSecurityGroups(&elasticache.DescribeCacheSecurityGroupsInput{ + CacheSecurityGroupName: aws.String(rs.Primary.ID), + }) + if err != nil { + return fmt.Errorf("CacheSecurityGroup error: %v", err) + } + return nil + } +} + +var testAccAWSEcacheSecurityGroupConfig = fmt.Sprintf(` +resource "aws_security_group" "bar" { + name = "tf-test-security-group-%03d" + description = "tf-test-security-group-descr" + ingress { + from_port = -1 + to_port = -1 + protocol = "icmp" + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_elasticache_security_group" "bar" { + name = "tf-test-security-group-%03d" + description = "tf-test-security-group-descr" + security_group_names = ["${aws_security_group.bar.name}"] +} +`, genRandInt(), genRandInt()) diff --git a/builtin/providers/aws/resource_aws_elasticache_subnet_group.go b/builtin/providers/aws/resource_aws_elasticache_subnet_group.go new file mode 100644 index 000000000..eb74de283 --- /dev/null +++ b/builtin/providers/aws/resource_aws_elasticache_subnet_group.go @@ -0,0 +1,137 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "github.com/awslabs/aws-sdk-go/service/elasticache" + "github.com/hashicorp/aws-sdk-go/aws" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsElasticacheSubnetGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsElasticacheSubnetGroupCreate, + Read: resourceAwsElasticacheSubnetGroupRead, + Delete: resourceAwsElasticacheSubnetGroupDelete, + + Schema: map[string]*schema.Schema{ + "description": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "subnet_ids": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: func(v interface{}) int { + return hashcode.String(v.(string)) + }, + }, + }, + } +} + +func resourceAwsElasticacheSubnetGroupCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).elasticacheconn + + // Get the group properties + name := d.Get("name").(string) + desc := d.Get("description").(string) + subnetIdsSet := d.Get("subnet_ids").(*schema.Set) + + log.Printf("[DEBUG] Cache subnet group create: name: %s, description: %s", name, desc) + + subnetIds := expandStringList(subnetIdsSet.List()) + + req := &elasticache.CreateCacheSubnetGroupInput{ + CacheSubnetGroupDescription: aws.String(desc), + CacheSubnetGroupName: aws.String(name), + SubnetIDs: subnetIds, + } + + _, err := conn.CreateCacheSubnetGroup(req) + if err != nil { + return fmt.Errorf("Error creating CacheSubnetGroup: %s", err) + } + + // Assign the group name as the resource ID + d.SetId(name) + + return nil +} + +func resourceAwsElasticacheSubnetGroupRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).elasticacheconn + req := &elasticache.DescribeCacheSubnetGroupsInput{ + CacheSubnetGroupName: aws.String(d.Get("name").(string)), + } + + res, err := conn.DescribeCacheSubnetGroups(req) + if err != nil { + return err + } + if len(res.CacheSubnetGroups) == 0 { + return fmt.Errorf("Error missing %v", d.Get("name")) + } + + var group *elasticache.CacheSubnetGroup + for _, g := range res.CacheSubnetGroups { + log.Printf("[DEBUG] %v %v", g.CacheSubnetGroupName, d.Id()) + if *g.CacheSubnetGroupName == d.Id() { + group = g + } + } + if group == nil { + return fmt.Errorf("Error retrieving cache subnet group: %v", res) + } + + ids := make([]string, len(group.Subnets)) + for i, s := range group.Subnets { + ids[i] = *s.SubnetIdentifier + } + + d.Set("name", group.CacheSubnetGroupName) + d.Set("description", group.CacheSubnetGroupDescription) + d.Set("subnet_ids", ids) + + return nil +} + +func resourceAwsElasticacheSubnetGroupDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).elasticacheconn + + log.Printf("[DEBUG] Cache subnet group delete: %s", d.Id()) + + return resource.Retry(5*time.Minute, func() error { + _, err := conn.DeleteCacheSubnetGroup(&elasticache.DeleteCacheSubnetGroupInput{ + CacheSubnetGroupName: aws.String(d.Id()), + }) + if err != nil { + apierr, ok := err.(aws.APIError) + if !ok { + return err + } + log.Printf("[DEBUG] APIError.Code: %v", apierr.Code) + switch apierr.Code { + case "DependencyViolation": + // If it is a dependency violation, we want to retry + return err + default: + return resource.RetryError{Err: err} + } + } + return nil + }) +} diff --git a/builtin/providers/aws/resource_aws_elasticache_subnet_group_test.go b/builtin/providers/aws/resource_aws_elasticache_subnet_group_test.go new file mode 100644 index 000000000..bb6700c33 --- /dev/null +++ b/builtin/providers/aws/resource_aws_elasticache_subnet_group_test.go @@ -0,0 +1,93 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/elasticache" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSEcacheSubnetGroup(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEcacheSubnetGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSEcacheSubnetGroupConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcacheSubnetGroupExists("aws_elasticache_subnet_group.bar"), + ), + }, + }, + }) +} + +func testAccCheckAWSEcacheSubnetGroupDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).elasticacheconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_elasticache_subnet_group" { + continue + } + res, err := conn.DescribeCacheSubnetGroups(&elasticache.DescribeCacheSubnetGroupsInput{ + CacheSubnetGroupName: aws.String(rs.Primary.ID), + }) + if err != nil { + return err + } + if len(res.CacheSubnetGroups) > 0 { + return fmt.Errorf("still exist.") + } + } + return nil +} + +func testAccCheckAWSEcacheSubnetGroupExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No cache subnet group ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).elasticacheconn + _, err := conn.DescribeCacheSubnetGroups(&elasticache.DescribeCacheSubnetGroupsInput{ + CacheSubnetGroupName: aws.String(rs.Primary.ID), + }) + if err != nil { + return fmt.Errorf("CacheSubnetGroup error: %v", err) + } + return nil + } +} + +var testAccAWSEcacheSubnetGroupConfig = fmt.Sprintf(` +resource "aws_vpc" "foo" { + cidr_block = "192.168.1.1/16" + tags { + Name = "tf-test" + } +} + +resource "aws_subnet" "foo" { + vpc_id = "${aws_vpc.foo.id}" + cidr_block = "192.168.1.1/20" + availability_zone = "us-west-2a" + tags { + Name = "tf-test" + } +} + +resource "aws_elasticache_subnet_group" "bar" { + name = "tf-test-cache-subnet-%03d" + description = "tf-test-cache-subnet-group-descr" + subnet_ids = ["${aws_subnet.foo.id}"] +} +`, genRandInt()) diff --git a/builtin/providers/aws/resource_aws_elasticache_test.go b/builtin/providers/aws/resource_aws_elasticache_test.go new file mode 100644 index 000000000..567b4be8b --- /dev/null +++ b/builtin/providers/aws/resource_aws_elasticache_test.go @@ -0,0 +1,159 @@ +package aws + +import ( + "fmt" + "math/rand" + "testing" + "time" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/elasticache" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSElasticache(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSElasticacheDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSElasticacheConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcacheSecurityGroupExists("aws_elasticache_security_group.bar"), + testAccCheckAWSElasticacheExists("aws_elasticache.bar"), + ), + }, + resource.TestStep{ + Config: testAccAWSElasticacheInVPCConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcacheSubnetGroupExists("aws_elasticache_subnet_group.bar"), + testAccCheckAWSElasticacheExists("aws_elasticache.bar"), + ), + }, + }, + }) +} + +func testAccCheckAWSElasticacheDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).elasticacheconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_elasticache" { + continue + } + res, err := conn.DescribeCacheClusters(&elasticache.DescribeCacheClustersInput{ + CacheClusterID: aws.String(rs.Primary.ID), + }) + if err != nil { + return err + } + if len(res.CacheClusters) > 0 { + return fmt.Errorf("still exist.") + } + } + return nil +} + +func testAccCheckAWSElasticacheExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No cache cluster ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).elasticacheconn + _, err := conn.DescribeCacheClusters(&elasticache.DescribeCacheClustersInput{ + CacheClusterID: aws.String(rs.Primary.ID), + }) + if err != nil { + return fmt.Errorf("Elasticache error: %v", err) + } + return nil + } +} + +func genRandInt() int { + return rand.New(rand.NewSource(time.Now().UnixNano())).Int() % 1000 +} + +var testAccAWSElasticacheConfig = fmt.Sprintf(` +resource "aws_security_group" "bar" { + name = "tf-test-security-group-%03d" + description = "tf-test-security-group-descr" + ingress { + from_port = -1 + to_port = -1 + protocol = "icmp" + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_elasticache_security_group" "bar" { + name = "tf-test-security-group-%03d" + description = "tf-test-security-group-descr" + security_group_names = ["${aws_security_group.bar.name}"] +} + +resource "aws_elasticache" "bar" { + cluster_id = "tf-test-%03d" + engine = "memcached" + node_type = "cache.m1.small" + num_cache_nodes = 1 + parameter_group_name = "default.memcached1.4" + security_group_names = ["${aws_elasticache_security_group.bar.name}"] +} +`, genRandInt(), genRandInt(), genRandInt()) + +var testAccAWSElasticacheInVPCConfig = fmt.Sprintf(` +resource "aws_vpc" "foo" { + cidr_block = "192.168.0.0/16" + tags { + Name = "tf-test" + } +} + +resource "aws_subnet" "foo" { + vpc_id = "${aws_vpc.foo.id}" + cidr_block = "192.168.0.0/20" + availability_zone = "us-west-2a" + tags { + Name = "tf-test" + } +} + +resource "aws_elasticache_subnet_group" "bar" { + name = "tf-test-cache-subnet-%03d" + description = "tf-test-cache-subnet-group-descr" + subnet_ids = ["${aws_subnet.foo.id}"] +} + +resource "aws_security_group" "bar" { + name = "tf-test-security-group-%03d" + description = "tf-test-security-group-descr" + vpc_id = "${aws_vpc.foo.id}" + ingress { + from_port = -1 + to_port = -1 + protocol = "icmp" + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_elasticache" "bar" { + cluster_id = "tf-test-%03d" + node_type = "cache.m1.small" + num_cache_nodes = 1 + engine = "redis" + engine_version = "2.8.19" + port = 6379 + subnet_group_name = "${aws_elasticache_subnet_group.bar.name}" + security_group_ids = ["${aws_security_group.bar.id}"] + parameter_group_name = "default.redis2.8" +} +`, genRandInt(), genRandInt(), genRandInt())