github.com/armen/terraform@v0.5.2-0.20150529052519-caa8117a08f1/builtin/providers/aws/resource_aws_elasticache_cluster.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "sort" 7 "strings" 8 "time" 9 10 "github.com/awslabs/aws-sdk-go/aws" 11 "github.com/awslabs/aws-sdk-go/aws/awserr" 12 "github.com/awslabs/aws-sdk-go/service/elasticache" 13 "github.com/awslabs/aws-sdk-go/service/iam" 14 "github.com/hashicorp/terraform/helper/hashcode" 15 "github.com/hashicorp/terraform/helper/resource" 16 "github.com/hashicorp/terraform/helper/schema" 17 ) 18 19 func resourceAwsElasticacheCluster() *schema.Resource { 20 return &schema.Resource{ 21 Create: resourceAwsElasticacheClusterCreate, 22 Read: resourceAwsElasticacheClusterRead, 23 Update: resourceAwsElasticacheClusterUpdate, 24 Delete: resourceAwsElasticacheClusterDelete, 25 26 Schema: map[string]*schema.Schema{ 27 "cluster_id": &schema.Schema{ 28 Type: schema.TypeString, 29 Required: true, 30 ForceNew: true, 31 }, 32 "engine": &schema.Schema{ 33 Type: schema.TypeString, 34 Required: true, 35 ForceNew: true, 36 }, 37 "node_type": &schema.Schema{ 38 Type: schema.TypeString, 39 Required: true, 40 ForceNew: true, 41 }, 42 "num_cache_nodes": &schema.Schema{ 43 Type: schema.TypeInt, 44 Required: true, 45 ForceNew: true, 46 }, 47 "parameter_group_name": &schema.Schema{ 48 Type: schema.TypeString, 49 Optional: true, 50 Computed: true, 51 ForceNew: true, 52 }, 53 "port": &schema.Schema{ 54 Type: schema.TypeInt, 55 Default: 11211, 56 Optional: true, 57 ForceNew: true, 58 }, 59 "engine_version": &schema.Schema{ 60 Type: schema.TypeString, 61 Optional: true, 62 Computed: true, 63 ForceNew: true, 64 }, 65 "subnet_group_name": &schema.Schema{ 66 Type: schema.TypeString, 67 Optional: true, 68 Computed: true, 69 ForceNew: true, 70 }, 71 "security_group_names": &schema.Schema{ 72 Type: schema.TypeSet, 73 Optional: true, 74 Computed: true, 75 ForceNew: true, 76 Elem: &schema.Schema{Type: schema.TypeString}, 77 Set: func(v interface{}) int { 78 return hashcode.String(v.(string)) 79 }, 80 }, 81 "security_group_ids": &schema.Schema{ 82 Type: schema.TypeSet, 83 Optional: true, 84 Computed: true, 85 ForceNew: true, 86 Elem: &schema.Schema{Type: schema.TypeString}, 87 Set: func(v interface{}) int { 88 return hashcode.String(v.(string)) 89 }, 90 }, 91 // Exported Attributes 92 "cache_nodes": &schema.Schema{ 93 Type: schema.TypeList, 94 Computed: true, 95 Elem: &schema.Resource{ 96 Schema: map[string]*schema.Schema{ 97 "id": &schema.Schema{ 98 Type: schema.TypeString, 99 Computed: true, 100 }, 101 "address": &schema.Schema{ 102 Type: schema.TypeString, 103 Computed: true, 104 }, 105 "port": &schema.Schema{ 106 Type: schema.TypeInt, 107 Computed: true, 108 }, 109 }, 110 }, 111 }, 112 113 "tags": tagsSchema(), 114 }, 115 } 116 } 117 118 func resourceAwsElasticacheClusterCreate(d *schema.ResourceData, meta interface{}) error { 119 conn := meta.(*AWSClient).elasticacheconn 120 121 clusterId := d.Get("cluster_id").(string) 122 nodeType := d.Get("node_type").(string) // e.g) cache.m1.small 123 numNodes := int64(d.Get("num_cache_nodes").(int)) // 2 124 engine := d.Get("engine").(string) // memcached 125 engineVersion := d.Get("engine_version").(string) // 1.4.14 126 port := int64(d.Get("port").(int)) // 11211 127 subnetGroupName := d.Get("subnet_group_name").(string) 128 securityNameSet := d.Get("security_group_names").(*schema.Set) 129 securityIdSet := d.Get("security_group_ids").(*schema.Set) 130 131 securityNames := expandStringList(securityNameSet.List()) 132 securityIds := expandStringList(securityIdSet.List()) 133 134 tags := tagsFromMapEC(d.Get("tags").(map[string]interface{})) 135 req := &elasticache.CreateCacheClusterInput{ 136 CacheClusterID: aws.String(clusterId), 137 CacheNodeType: aws.String(nodeType), 138 NumCacheNodes: aws.Long(numNodes), 139 Engine: aws.String(engine), 140 EngineVersion: aws.String(engineVersion), 141 Port: aws.Long(port), 142 CacheSubnetGroupName: aws.String(subnetGroupName), 143 CacheSecurityGroupNames: securityNames, 144 SecurityGroupIDs: securityIds, 145 Tags: tags, 146 } 147 148 // parameter groups are optional and can be defaulted by AWS 149 if v, ok := d.GetOk("parameter_group_name"); ok { 150 req.CacheParameterGroupName = aws.String(v.(string)) 151 } 152 153 _, err := conn.CreateCacheCluster(req) 154 if err != nil { 155 return fmt.Errorf("Error creating Elasticache: %s", err) 156 } 157 158 pending := []string{"creating"} 159 stateConf := &resource.StateChangeConf{ 160 Pending: pending, 161 Target: "available", 162 Refresh: CacheClusterStateRefreshFunc(conn, d.Id(), "available", pending), 163 Timeout: 10 * time.Minute, 164 Delay: 10 * time.Second, 165 MinTimeout: 3 * time.Second, 166 } 167 168 log.Printf("[DEBUG] Waiting for state to become available: %v", d.Id()) 169 _, sterr := stateConf.WaitForState() 170 if sterr != nil { 171 return fmt.Errorf("Error waiting for elasticache (%s) to be created: %s", d.Id(), sterr) 172 } 173 174 d.SetId(clusterId) 175 176 return resourceAwsElasticacheClusterRead(d, meta) 177 } 178 179 func resourceAwsElasticacheClusterRead(d *schema.ResourceData, meta interface{}) error { 180 conn := meta.(*AWSClient).elasticacheconn 181 req := &elasticache.DescribeCacheClustersInput{ 182 CacheClusterID: aws.String(d.Id()), 183 ShowCacheNodeInfo: aws.Boolean(true), 184 } 185 186 res, err := conn.DescribeCacheClusters(req) 187 if err != nil { 188 return err 189 } 190 191 if len(res.CacheClusters) == 1 { 192 c := res.CacheClusters[0] 193 d.Set("cluster_id", c.CacheClusterID) 194 d.Set("node_type", c.CacheNodeType) 195 d.Set("num_cache_nodes", c.NumCacheNodes) 196 d.Set("engine", c.Engine) 197 d.Set("engine_version", c.EngineVersion) 198 if c.ConfigurationEndpoint != nil { 199 d.Set("port", c.ConfigurationEndpoint.Port) 200 } 201 d.Set("subnet_group_name", c.CacheSubnetGroupName) 202 d.Set("security_group_names", c.CacheSecurityGroups) 203 d.Set("security_group_ids", c.SecurityGroups) 204 d.Set("parameter_group_name", c.CacheParameterGroup) 205 206 if err := setCacheNodeData(d, c); err != nil { 207 return err 208 } 209 // list tags for resource 210 // set tags 211 arn, err := buildECARN(d, meta) 212 if err != nil { 213 log.Printf("[DEBUG] Error building ARN for ElastiCache Cluster, not setting Tags for cluster %s", *c.CacheClusterID) 214 } else { 215 resp, err := conn.ListTagsForResource(&elasticache.ListTagsForResourceInput{ 216 ResourceName: aws.String(arn), 217 }) 218 219 if err != nil { 220 log.Printf("[DEBUG] Error retreiving tags for ARN: %s", arn) 221 } 222 223 var et []*elasticache.Tag 224 if len(resp.TagList) > 0 { 225 et = resp.TagList 226 } 227 d.Set("tags", tagsToMapEC(et)) 228 } 229 } 230 231 return nil 232 } 233 234 func resourceAwsElasticacheClusterUpdate(d *schema.ResourceData, meta interface{}) error { 235 conn := meta.(*AWSClient).elasticacheconn 236 arn, err := buildECARN(d, meta) 237 if err != nil { 238 log.Printf("[DEBUG] Error building ARN for ElastiCache Cluster, not updating Tags for cluster %s", d.Id()) 239 } else { 240 if err := setTagsEC(conn, d, arn); err != nil { 241 return err 242 } 243 } 244 return resourceAwsElasticacheClusterRead(d, meta) 245 } 246 247 func setCacheNodeData(d *schema.ResourceData, c *elasticache.CacheCluster) error { 248 sortedCacheNodes := make([]*elasticache.CacheNode, len(c.CacheNodes)) 249 copy(sortedCacheNodes, c.CacheNodes) 250 sort.Sort(byCacheNodeId(sortedCacheNodes)) 251 252 cacheNodeData := make([]map[string]interface{}, 0, len(sortedCacheNodes)) 253 254 for _, node := range sortedCacheNodes { 255 if node.CacheNodeID == nil || node.Endpoint == nil || node.Endpoint.Address == nil || node.Endpoint.Port == nil { 256 return fmt.Errorf("Unexpected nil pointer in: %#v", node) 257 } 258 cacheNodeData = append(cacheNodeData, map[string]interface{}{ 259 "id": *node.CacheNodeID, 260 "address": *node.Endpoint.Address, 261 "port": int(*node.Endpoint.Port), 262 }) 263 } 264 265 return d.Set("cache_nodes", cacheNodeData) 266 } 267 268 type byCacheNodeId []*elasticache.CacheNode 269 270 func (b byCacheNodeId) Len() int { return len(b) } 271 func (b byCacheNodeId) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 272 func (b byCacheNodeId) Less(i, j int) bool { 273 return b[i].CacheNodeID != nil && b[j].CacheNodeID != nil && 274 *b[i].CacheNodeID < *b[j].CacheNodeID 275 } 276 277 func resourceAwsElasticacheClusterDelete(d *schema.ResourceData, meta interface{}) error { 278 conn := meta.(*AWSClient).elasticacheconn 279 280 req := &elasticache.DeleteCacheClusterInput{ 281 CacheClusterID: aws.String(d.Id()), 282 } 283 _, err := conn.DeleteCacheCluster(req) 284 if err != nil { 285 return err 286 } 287 288 log.Printf("[DEBUG] Waiting for deletion: %v", d.Id()) 289 stateConf := &resource.StateChangeConf{ 290 Pending: []string{"creating", "available", "deleting", "incompatible-parameters", "incompatible-network", "restore-failed"}, 291 Target: "", 292 Refresh: CacheClusterStateRefreshFunc(conn, d.Id(), "", []string{}), 293 Timeout: 10 * time.Minute, 294 Delay: 10 * time.Second, 295 MinTimeout: 3 * time.Second, 296 } 297 298 _, sterr := stateConf.WaitForState() 299 if sterr != nil { 300 return fmt.Errorf("Error waiting for elasticache (%s) to delete: %s", d.Id(), sterr) 301 } 302 303 d.SetId("") 304 305 return nil 306 } 307 308 func CacheClusterStateRefreshFunc(conn *elasticache.ElastiCache, clusterID, givenState string, pending []string) resource.StateRefreshFunc { 309 return func() (interface{}, string, error) { 310 resp, err := conn.DescribeCacheClusters(&elasticache.DescribeCacheClustersInput{ 311 CacheClusterID: aws.String(clusterID), 312 }) 313 if err != nil { 314 apierr := err.(awserr.Error) 315 log.Printf("[DEBUG] message: %v, code: %v", apierr.Message(), apierr.Code()) 316 if apierr.Message() == fmt.Sprintf("CacheCluster not found: %v", clusterID) { 317 log.Printf("[DEBUG] Detect deletion") 318 return nil, "", nil 319 } 320 321 log.Printf("[ERROR] CacheClusterStateRefreshFunc: %s", err) 322 return nil, "", err 323 } 324 325 c := resp.CacheClusters[0] 326 log.Printf("[DEBUG] status: %v", *c.CacheClusterStatus) 327 328 // return the current state if it's in the pending array 329 for _, p := range pending { 330 s := *c.CacheClusterStatus 331 if p == s { 332 log.Printf("[DEBUG] Return with status: %v", *c.CacheClusterStatus) 333 return c, p, nil 334 } 335 } 336 337 // return given state if it's not in pending 338 if givenState != "" { 339 return c, givenState, nil 340 } 341 log.Printf("[DEBUG] current status: %v", *c.CacheClusterStatus) 342 return c, *c.CacheClusterStatus, nil 343 } 344 } 345 346 func buildECARN(d *schema.ResourceData, meta interface{}) (string, error) { 347 iamconn := meta.(*AWSClient).iamconn 348 region := meta.(*AWSClient).region 349 // An zero value GetUserInput{} defers to the currently logged in user 350 resp, err := iamconn.GetUser(&iam.GetUserInput{}) 351 if err != nil { 352 return "", err 353 } 354 userARN := *resp.User.ARN 355 accountID := strings.Split(userARN, ":")[4] 356 arn := fmt.Sprintf("arn:aws:elasticache:%s:%s:cluster:%s", region, accountID, d.Id()) 357 return arn, nil 358 }