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  }