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