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