241 lines
6.9 KiB
Go
241 lines
6.9 KiB
Go
|
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
|
||
|
}
|
||
|
}
|