github.com/erriapo/terraform@v0.6.12-0.20160203182612-0340ea72354f/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:     []string{"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:     []string{"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:     []string{},
   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  }