github.com/minamijoyo/terraform@v0.7.8-0.20161029001309-18b3736ba44b/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/aws/aws-sdk-go/aws" 11 "github.com/aws/aws-sdk-go/aws/awserr" 12 "github.com/aws/aws-sdk-go/service/elasticache" 13 "github.com/hashicorp/terraform/helper/resource" 14 "github.com/hashicorp/terraform/helper/schema" 15 ) 16 17 func resourceAwsElastiCacheCommonSchema() map[string]*schema.Schema { 18 19 return map[string]*schema.Schema{ 20 "availability_zones": &schema.Schema{ 21 Type: schema.TypeSet, 22 Optional: true, 23 ForceNew: true, 24 Elem: &schema.Schema{Type: schema.TypeString}, 25 Set: schema.HashString, 26 }, 27 "node_type": &schema.Schema{ 28 Type: schema.TypeString, 29 Required: true, 30 }, 31 "engine": &schema.Schema{ 32 Type: schema.TypeString, 33 Required: true, 34 }, 35 "engine_version": &schema.Schema{ 36 Type: schema.TypeString, 37 Optional: true, 38 Computed: true, 39 }, 40 "parameter_group_name": &schema.Schema{ 41 Type: schema.TypeString, 42 Optional: true, 43 Computed: true, 44 }, 45 "subnet_group_name": &schema.Schema{ 46 Type: schema.TypeString, 47 Optional: true, 48 Computed: true, 49 ForceNew: true, 50 }, 51 "security_group_names": &schema.Schema{ 52 Type: schema.TypeSet, 53 Optional: true, 54 Computed: true, 55 ForceNew: true, 56 Elem: &schema.Schema{Type: schema.TypeString}, 57 Set: schema.HashString, 58 }, 59 "security_group_ids": &schema.Schema{ 60 Type: schema.TypeSet, 61 Optional: true, 62 Computed: true, 63 Elem: &schema.Schema{Type: schema.TypeString}, 64 Set: schema.HashString, 65 }, 66 // A single-element string list containing an Amazon Resource Name (ARN) that 67 // uniquely identifies a Redis RDB snapshot file stored in Amazon S3. The snapshot 68 // file will be used to populate the node group. 69 // 70 // See also: 71 // https://github.com/aws/aws-sdk-go/blob/4862a174f7fc92fb523fc39e68f00b87d91d2c3d/service/elasticache/api.go#L2079 72 "snapshot_arns": &schema.Schema{ 73 Type: schema.TypeSet, 74 Optional: true, 75 ForceNew: true, 76 Elem: &schema.Schema{Type: schema.TypeString}, 77 Set: schema.HashString, 78 }, 79 "snapshot_window": &schema.Schema{ 80 Type: schema.TypeString, 81 Optional: true, 82 Computed: true, 83 }, 84 "snapshot_name": &schema.Schema{ 85 Type: schema.TypeString, 86 Optional: true, 87 ForceNew: true, 88 }, 89 90 "maintenance_window": &schema.Schema{ 91 Type: schema.TypeString, 92 Optional: true, 93 Computed: true, 94 StateFunc: func(val interface{}) string { 95 // Elasticache always changes the maintenance 96 // to lowercase 97 return strings.ToLower(val.(string)) 98 }, 99 }, 100 "port": &schema.Schema{ 101 Type: schema.TypeInt, 102 Required: true, 103 ForceNew: true, 104 }, 105 "notification_topic_arn": &schema.Schema{ 106 Type: schema.TypeString, 107 Optional: true, 108 }, 109 110 "snapshot_retention_limit": &schema.Schema{ 111 Type: schema.TypeInt, 112 Optional: true, 113 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 114 value := v.(int) 115 if value > 35 { 116 es = append(es, fmt.Errorf( 117 "snapshot retention limit cannot be more than 35 days")) 118 } 119 return 120 }, 121 }, 122 123 "apply_immediately": &schema.Schema{ 124 Type: schema.TypeBool, 125 Optional: true, 126 Computed: true, 127 }, 128 129 "tags": tagsSchema(), 130 } 131 } 132 133 func resourceAwsElasticacheCluster() *schema.Resource { 134 resourceSchema := resourceAwsElastiCacheCommonSchema() 135 136 resourceSchema["cluster_id"] = &schema.Schema{ 137 Type: schema.TypeString, 138 Required: true, 139 ForceNew: true, 140 StateFunc: func(val interface{}) string { 141 // Elasticache normalizes cluster ids to lowercase, 142 // so we have to do this too or else we can end up 143 // with non-converging diffs. 144 return strings.ToLower(val.(string)) 145 }, 146 ValidateFunc: validateElastiCacheClusterId, 147 } 148 149 resourceSchema["num_cache_nodes"] = &schema.Schema{ 150 Type: schema.TypeInt, 151 Required: true, 152 } 153 154 resourceSchema["az_mode"] = &schema.Schema{ 155 Type: schema.TypeString, 156 Optional: true, 157 Computed: true, 158 ForceNew: true, 159 } 160 161 resourceSchema["availability_zone"] = &schema.Schema{ 162 Type: schema.TypeString, 163 Optional: true, 164 Computed: true, 165 ForceNew: true, 166 } 167 168 resourceSchema["configuration_endpoint"] = &schema.Schema{ 169 Type: schema.TypeString, 170 Computed: true, 171 } 172 173 resourceSchema["cluster_address"] = &schema.Schema{ 174 Type: schema.TypeString, 175 Computed: true, 176 } 177 178 resourceSchema["replication_group_id"] = &schema.Schema{ 179 Type: schema.TypeString, 180 Computed: true, 181 } 182 183 resourceSchema["cache_nodes"] = &schema.Schema{ 184 Type: schema.TypeList, 185 Computed: true, 186 Elem: &schema.Resource{ 187 Schema: map[string]*schema.Schema{ 188 "id": &schema.Schema{ 189 Type: schema.TypeString, 190 Computed: true, 191 }, 192 "address": &schema.Schema{ 193 Type: schema.TypeString, 194 Computed: true, 195 }, 196 "port": &schema.Schema{ 197 Type: schema.TypeInt, 198 Computed: true, 199 }, 200 "availability_zone": &schema.Schema{ 201 Type: schema.TypeString, 202 Computed: true, 203 }, 204 }, 205 }, 206 } 207 208 return &schema.Resource{ 209 Create: resourceAwsElasticacheClusterCreate, 210 Read: resourceAwsElasticacheClusterRead, 211 Update: resourceAwsElasticacheClusterUpdate, 212 Delete: resourceAwsElasticacheClusterDelete, 213 Importer: &schema.ResourceImporter{ 214 State: schema.ImportStatePassthrough, 215 }, 216 217 Schema: resourceSchema, 218 } 219 } 220 221 func resourceAwsElasticacheClusterCreate(d *schema.ResourceData, meta interface{}) error { 222 conn := meta.(*AWSClient).elasticacheconn 223 224 clusterId := d.Get("cluster_id").(string) 225 nodeType := d.Get("node_type").(string) // e.g) cache.m1.small 226 numNodes := int64(d.Get("num_cache_nodes").(int)) // 2 227 engine := d.Get("engine").(string) // memcached 228 engineVersion := d.Get("engine_version").(string) // 1.4.14 229 port := int64(d.Get("port").(int)) // e.g) 11211 230 subnetGroupName := d.Get("subnet_group_name").(string) 231 securityNameSet := d.Get("security_group_names").(*schema.Set) 232 securityIdSet := d.Get("security_group_ids").(*schema.Set) 233 234 securityNames := expandStringList(securityNameSet.List()) 235 securityIds := expandStringList(securityIdSet.List()) 236 tags := tagsFromMapEC(d.Get("tags").(map[string]interface{})) 237 238 req := &elasticache.CreateCacheClusterInput{ 239 CacheClusterId: aws.String(clusterId), 240 CacheNodeType: aws.String(nodeType), 241 NumCacheNodes: aws.Int64(numNodes), 242 Engine: aws.String(engine), 243 EngineVersion: aws.String(engineVersion), 244 Port: aws.Int64(port), 245 CacheSubnetGroupName: aws.String(subnetGroupName), 246 CacheSecurityGroupNames: securityNames, 247 SecurityGroupIds: securityIds, 248 Tags: tags, 249 } 250 251 // parameter groups are optional and can be defaulted by AWS 252 if v, ok := d.GetOk("parameter_group_name"); ok { 253 req.CacheParameterGroupName = aws.String(v.(string)) 254 } 255 256 if v, ok := d.GetOk("snapshot_retention_limit"); ok { 257 req.SnapshotRetentionLimit = aws.Int64(int64(v.(int))) 258 } 259 260 if v, ok := d.GetOk("snapshot_window"); ok { 261 req.SnapshotWindow = aws.String(v.(string)) 262 } 263 264 if v, ok := d.GetOk("maintenance_window"); ok { 265 req.PreferredMaintenanceWindow = aws.String(v.(string)) 266 } 267 268 if v, ok := d.GetOk("notification_topic_arn"); ok { 269 req.NotificationTopicArn = aws.String(v.(string)) 270 } 271 272 snaps := d.Get("snapshot_arns").(*schema.Set).List() 273 if len(snaps) > 0 { 274 s := expandStringList(snaps) 275 req.SnapshotArns = s 276 log.Printf("[DEBUG] Restoring Redis cluster from S3 snapshot: %#v", s) 277 } 278 279 if v, ok := d.GetOk("snapshot_name"); ok { 280 req.SnapshotName = aws.String(v.(string)) 281 } 282 283 if v, ok := d.GetOk("az_mode"); ok { 284 req.AZMode = aws.String(v.(string)) 285 } 286 287 if v, ok := d.GetOk("availability_zone"); ok { 288 req.PreferredAvailabilityZone = aws.String(v.(string)) 289 } 290 291 preferred_azs := d.Get("availability_zones").(*schema.Set).List() 292 if len(preferred_azs) > 0 { 293 azs := expandStringList(preferred_azs) 294 req.PreferredAvailabilityZones = azs 295 } 296 297 if v, ok := d.GetOk("replication_group_id"); ok { 298 req.ReplicationGroupId = aws.String(v.(string)) 299 } 300 301 resp, err := conn.CreateCacheCluster(req) 302 if err != nil { 303 return fmt.Errorf("Error creating Elasticache: %s", err) 304 } 305 306 // Assign the cluster id as the resource ID 307 // Elasticache always retains the id in lower case, so we have to 308 // mimic that or else we won't be able to refresh a resource whose 309 // name contained uppercase characters. 310 d.SetId(strings.ToLower(*resp.CacheCluster.CacheClusterId)) 311 312 pending := []string{"creating", "modifying", "restoring"} 313 stateConf := &resource.StateChangeConf{ 314 Pending: pending, 315 Target: []string{"available"}, 316 Refresh: cacheClusterStateRefreshFunc(conn, d.Id(), "available", pending), 317 Timeout: 40 * time.Minute, 318 MinTimeout: 10 * time.Second, 319 Delay: 30 * time.Second, 320 } 321 322 log.Printf("[DEBUG] Waiting for state to become available: %v", d.Id()) 323 _, sterr := stateConf.WaitForState() 324 if sterr != nil { 325 return fmt.Errorf("Error waiting for elasticache (%s) to be created: %s", d.Id(), sterr) 326 } 327 328 return resourceAwsElasticacheClusterRead(d, meta) 329 } 330 331 func resourceAwsElasticacheClusterRead(d *schema.ResourceData, meta interface{}) error { 332 conn := meta.(*AWSClient).elasticacheconn 333 req := &elasticache.DescribeCacheClustersInput{ 334 CacheClusterId: aws.String(d.Id()), 335 ShowCacheNodeInfo: aws.Bool(true), 336 } 337 338 res, err := conn.DescribeCacheClusters(req) 339 if err != nil { 340 if eccErr, ok := err.(awserr.Error); ok && eccErr.Code() == "CacheClusterNotFound" { 341 log.Printf("[WARN] ElastiCache Cluster (%s) not found", d.Id()) 342 d.SetId("") 343 return nil 344 } 345 346 return err 347 } 348 349 if len(res.CacheClusters) == 1 { 350 c := res.CacheClusters[0] 351 d.Set("cluster_id", c.CacheClusterId) 352 d.Set("node_type", c.CacheNodeType) 353 d.Set("num_cache_nodes", c.NumCacheNodes) 354 d.Set("engine", c.Engine) 355 d.Set("engine_version", c.EngineVersion) 356 if c.ConfigurationEndpoint != nil { 357 d.Set("port", c.ConfigurationEndpoint.Port) 358 d.Set("configuration_endpoint", aws.String(fmt.Sprintf("%s:%d", *c.ConfigurationEndpoint.Address, *c.ConfigurationEndpoint.Port))) 359 d.Set("cluster_address", aws.String(fmt.Sprintf("%s", *c.ConfigurationEndpoint.Address))) 360 } 361 362 if c.ReplicationGroupId != nil { 363 d.Set("replication_group_id", c.ReplicationGroupId) 364 } 365 366 d.Set("subnet_group_name", c.CacheSubnetGroupName) 367 d.Set("security_group_names", flattenElastiCacheSecurityGroupNames(c.CacheSecurityGroups)) 368 d.Set("security_group_ids", flattenElastiCacheSecurityGroupIds(c.SecurityGroups)) 369 if c.CacheParameterGroup != nil { 370 d.Set("parameter_group_name", c.CacheParameterGroup.CacheParameterGroupName) 371 } 372 d.Set("maintenance_window", c.PreferredMaintenanceWindow) 373 d.Set("snapshot_window", c.SnapshotWindow) 374 d.Set("snapshot_retention_limit", c.SnapshotRetentionLimit) 375 if c.NotificationConfiguration != nil { 376 if *c.NotificationConfiguration.TopicStatus == "active" { 377 d.Set("notification_topic_arn", c.NotificationConfiguration.TopicArn) 378 } 379 } 380 d.Set("availability_zone", c.PreferredAvailabilityZone) 381 382 if err := setCacheNodeData(d, c); err != nil { 383 return err 384 } 385 // list tags for resource 386 // set tags 387 arn, err := buildECARN(d.Id(), meta.(*AWSClient).partition, meta.(*AWSClient).accountid, meta.(*AWSClient).region) 388 if err != nil { 389 log.Printf("[DEBUG] Error building ARN for ElastiCache Cluster, not setting Tags for cluster %s", *c.CacheClusterId) 390 } else { 391 resp, err := conn.ListTagsForResource(&elasticache.ListTagsForResourceInput{ 392 ResourceName: aws.String(arn), 393 }) 394 395 if err != nil { 396 log.Printf("[DEBUG] Error retrieving tags for ARN: %s", arn) 397 } 398 399 var et []*elasticache.Tag 400 if len(resp.TagList) > 0 { 401 et = resp.TagList 402 } 403 d.Set("tags", tagsToMapEC(et)) 404 } 405 } 406 407 return nil 408 } 409 410 func resourceAwsElasticacheClusterUpdate(d *schema.ResourceData, meta interface{}) error { 411 conn := meta.(*AWSClient).elasticacheconn 412 arn, err := buildECARN(d.Id(), meta.(*AWSClient).partition, meta.(*AWSClient).accountid, meta.(*AWSClient).region) 413 if err != nil { 414 log.Printf("[DEBUG] Error building ARN for ElastiCache Cluster, not updating Tags for cluster %s", d.Id()) 415 } else { 416 if err := setTagsEC(conn, d, arn); err != nil { 417 return err 418 } 419 } 420 421 req := &elasticache.ModifyCacheClusterInput{ 422 CacheClusterId: aws.String(d.Id()), 423 ApplyImmediately: aws.Bool(d.Get("apply_immediately").(bool)), 424 } 425 426 requestUpdate := false 427 if d.HasChange("security_group_ids") { 428 if attr := d.Get("security_group_ids").(*schema.Set); attr.Len() > 0 { 429 req.SecurityGroupIds = expandStringList(attr.List()) 430 requestUpdate = true 431 } 432 } 433 434 if d.HasChange("parameter_group_name") { 435 req.CacheParameterGroupName = aws.String(d.Get("parameter_group_name").(string)) 436 requestUpdate = true 437 } 438 439 if d.HasChange("maintenance_window") { 440 req.PreferredMaintenanceWindow = aws.String(d.Get("maintenance_window").(string)) 441 requestUpdate = true 442 } 443 444 if d.HasChange("notification_topic_arn") { 445 v := d.Get("notification_topic_arn").(string) 446 req.NotificationTopicArn = aws.String(v) 447 if v == "" { 448 inactive := "inactive" 449 req.NotificationTopicStatus = &inactive 450 } 451 requestUpdate = true 452 } 453 454 if d.HasChange("engine_version") { 455 req.EngineVersion = aws.String(d.Get("engine_version").(string)) 456 requestUpdate = true 457 } 458 459 if d.HasChange("snapshot_window") { 460 req.SnapshotWindow = aws.String(d.Get("snapshot_window").(string)) 461 requestUpdate = true 462 } 463 464 if d.HasChange("node_type") { 465 req.CacheNodeType = aws.String(d.Get("node_type").(string)) 466 requestUpdate = true 467 } 468 469 if d.HasChange("snapshot_retention_limit") { 470 req.SnapshotRetentionLimit = aws.Int64(int64(d.Get("snapshot_retention_limit").(int))) 471 requestUpdate = true 472 } 473 474 if d.HasChange("num_cache_nodes") { 475 oraw, nraw := d.GetChange("num_cache_nodes") 476 o := oraw.(int) 477 n := nraw.(int) 478 if v, ok := d.GetOk("az_mode"); ok && v.(string) == "cross-az" && n == 1 { 479 return fmt.Errorf("[WARN] Error updateing Elasticache cluster (%s), error: Cross-AZ mode is not supported in a single cache node.", d.Id()) 480 } 481 if n < o { 482 log.Printf("[INFO] Cluster %s is marked for Decreasing cache nodes from %d to %d", d.Id(), o, n) 483 nodesToRemove := getCacheNodesToRemove(d, o, o-n) 484 req.CacheNodeIdsToRemove = nodesToRemove 485 } 486 487 req.NumCacheNodes = aws.Int64(int64(d.Get("num_cache_nodes").(int))) 488 requestUpdate = true 489 490 } 491 492 if requestUpdate { 493 log.Printf("[DEBUG] Modifying ElastiCache Cluster (%s), opts:\n%s", d.Id(), req) 494 _, err := conn.ModifyCacheCluster(req) 495 if err != nil { 496 return fmt.Errorf("[WARN] Error updating ElastiCache cluster (%s), error: %s", d.Id(), err) 497 } 498 499 log.Printf("[DEBUG] Waiting for update: %s", d.Id()) 500 pending := []string{"modifying", "rebooting cache cluster nodes", "snapshotting"} 501 stateConf := &resource.StateChangeConf{ 502 Pending: pending, 503 Target: []string{"available"}, 504 Refresh: cacheClusterStateRefreshFunc(conn, d.Id(), "available", pending), 505 Timeout: 80 * time.Minute, 506 MinTimeout: 10 * time.Second, 507 Delay: 30 * time.Second, 508 } 509 510 _, sterr := stateConf.WaitForState() 511 if sterr != nil { 512 return fmt.Errorf("Error waiting for elasticache (%s) to update: %s", d.Id(), sterr) 513 } 514 } 515 516 return resourceAwsElasticacheClusterRead(d, meta) 517 } 518 519 func getCacheNodesToRemove(d *schema.ResourceData, oldNumberOfNodes int, cacheNodesToRemove int) []*string { 520 nodesIdsToRemove := []*string{} 521 for i := oldNumberOfNodes; i > oldNumberOfNodes-cacheNodesToRemove && i > 0; i-- { 522 s := fmt.Sprintf("%04d", i) 523 nodesIdsToRemove = append(nodesIdsToRemove, &s) 524 } 525 526 return nodesIdsToRemove 527 } 528 529 func setCacheNodeData(d *schema.ResourceData, c *elasticache.CacheCluster) error { 530 sortedCacheNodes := make([]*elasticache.CacheNode, len(c.CacheNodes)) 531 copy(sortedCacheNodes, c.CacheNodes) 532 sort.Sort(byCacheNodeId(sortedCacheNodes)) 533 534 cacheNodeData := make([]map[string]interface{}, 0, len(sortedCacheNodes)) 535 536 for _, node := range sortedCacheNodes { 537 if node.CacheNodeId == nil || node.Endpoint == nil || node.Endpoint.Address == nil || node.Endpoint.Port == nil || node.CustomerAvailabilityZone == nil { 538 return fmt.Errorf("Unexpected nil pointer in: %s", node) 539 } 540 cacheNodeData = append(cacheNodeData, map[string]interface{}{ 541 "id": *node.CacheNodeId, 542 "address": *node.Endpoint.Address, 543 "port": int(*node.Endpoint.Port), 544 "availability_zone": *node.CustomerAvailabilityZone, 545 }) 546 } 547 548 return d.Set("cache_nodes", cacheNodeData) 549 } 550 551 type byCacheNodeId []*elasticache.CacheNode 552 553 func (b byCacheNodeId) Len() int { return len(b) } 554 func (b byCacheNodeId) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 555 func (b byCacheNodeId) Less(i, j int) bool { 556 return b[i].CacheNodeId != nil && b[j].CacheNodeId != nil && 557 *b[i].CacheNodeId < *b[j].CacheNodeId 558 } 559 560 func resourceAwsElasticacheClusterDelete(d *schema.ResourceData, meta interface{}) error { 561 conn := meta.(*AWSClient).elasticacheconn 562 563 req := &elasticache.DeleteCacheClusterInput{ 564 CacheClusterId: aws.String(d.Id()), 565 } 566 _, err := conn.DeleteCacheCluster(req) 567 if err != nil { 568 return err 569 } 570 571 log.Printf("[DEBUG] Waiting for deletion: %v", d.Id()) 572 stateConf := &resource.StateChangeConf{ 573 Pending: []string{"creating", "available", "deleting", "incompatible-parameters", "incompatible-network", "restore-failed"}, 574 Target: []string{}, 575 Refresh: cacheClusterStateRefreshFunc(conn, d.Id(), "", []string{}), 576 Timeout: 40 * time.Minute, 577 MinTimeout: 10 * time.Second, 578 Delay: 30 * time.Second, 579 } 580 581 _, sterr := stateConf.WaitForState() 582 if sterr != nil { 583 return fmt.Errorf("Error waiting for elasticache (%s) to delete: %s", d.Id(), sterr) 584 } 585 586 d.SetId("") 587 588 return nil 589 } 590 591 func cacheClusterStateRefreshFunc(conn *elasticache.ElastiCache, clusterID, givenState string, pending []string) resource.StateRefreshFunc { 592 return func() (interface{}, string, error) { 593 resp, err := conn.DescribeCacheClusters(&elasticache.DescribeCacheClustersInput{ 594 CacheClusterId: aws.String(clusterID), 595 ShowCacheNodeInfo: aws.Bool(true), 596 }) 597 if err != nil { 598 apierr := err.(awserr.Error) 599 log.Printf("[DEBUG] message: %v, code: %v", apierr.Message(), apierr.Code()) 600 if apierr.Message() == fmt.Sprintf("CacheCluster not found: %v", clusterID) { 601 log.Printf("[DEBUG] Detect deletion") 602 return nil, "", nil 603 } 604 605 log.Printf("[ERROR] CacheClusterStateRefreshFunc: %s", err) 606 return nil, "", err 607 } 608 609 if len(resp.CacheClusters) == 0 { 610 return nil, "", fmt.Errorf("[WARN] Error: no Cache Clusters found for id (%s)", clusterID) 611 } 612 613 var c *elasticache.CacheCluster 614 for _, cluster := range resp.CacheClusters { 615 if *cluster.CacheClusterId == clusterID { 616 log.Printf("[DEBUG] Found matching ElastiCache cluster: %s", *cluster.CacheClusterId) 617 c = cluster 618 } 619 } 620 621 if c == nil { 622 return nil, "", fmt.Errorf("[WARN] Error: no matching Elastic Cache cluster for id (%s)", clusterID) 623 } 624 625 log.Printf("[DEBUG] ElastiCache Cluster (%s) status: %v", clusterID, *c.CacheClusterStatus) 626 627 // return the current state if it's in the pending array 628 for _, p := range pending { 629 log.Printf("[DEBUG] ElastiCache: checking pending state (%s) for cluster (%s), cluster status: %s", pending, clusterID, *c.CacheClusterStatus) 630 s := *c.CacheClusterStatus 631 if p == s { 632 log.Printf("[DEBUG] Return with status: %v", *c.CacheClusterStatus) 633 return c, p, nil 634 } 635 } 636 637 // return given state if it's not in pending 638 if givenState != "" { 639 log.Printf("[DEBUG] ElastiCache: checking given state (%s) of cluster (%s) against cluster status (%s)", givenState, clusterID, *c.CacheClusterStatus) 640 // check to make sure we have the node count we're expecting 641 if int64(len(c.CacheNodes)) != *c.NumCacheNodes { 642 log.Printf("[DEBUG] Node count is not what is expected: %d found, %d expected", len(c.CacheNodes), *c.NumCacheNodes) 643 return nil, "creating", nil 644 } 645 646 log.Printf("[DEBUG] Node count matched (%d)", len(c.CacheNodes)) 647 // loop the nodes and check their status as well 648 for _, n := range c.CacheNodes { 649 log.Printf("[DEBUG] Checking cache node for status: %s", n) 650 if n.CacheNodeStatus != nil && *n.CacheNodeStatus != "available" { 651 log.Printf("[DEBUG] Node (%s) is not yet available, status: %s", *n.CacheNodeId, *n.CacheNodeStatus) 652 return nil, "creating", nil 653 } 654 log.Printf("[DEBUG] Cache node not in expected state") 655 } 656 log.Printf("[DEBUG] ElastiCache returning given state (%s), cluster: %s", givenState, c) 657 return c, givenState, nil 658 } 659 log.Printf("[DEBUG] current status: %v", *c.CacheClusterStatus) 660 return c, *c.CacheClusterStatus, nil 661 } 662 } 663 664 func buildECARN(identifier, partition, accountid, region string) (string, error) { 665 if partition == "" { 666 return "", fmt.Errorf("Unable to construct ElastiCache ARN because of missing AWS partition") 667 } 668 if accountid == "" { 669 return "", fmt.Errorf("Unable to construct ElastiCache ARN because of missing AWS Account ID") 670 } 671 arn := fmt.Sprintf("arn:%s:elasticache:%s:%s:cluster:%s", partition, region, accountid, identifier) 672 return arn, nil 673 674 }