github.com/armen/terraform@v0.5.2-0.20150529052519-caa8117a08f1/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/awslabs/aws-sdk-go/aws"
    11  	"github.com/awslabs/aws-sdk-go/aws/awserr"
    12  	"github.com/awslabs/aws-sdk-go/service/elasticache"
    13  	"github.com/awslabs/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  			},
    32  			"engine": &schema.Schema{
    33  				Type:     schema.TypeString,
    34  				Required: true,
    35  				ForceNew: true,
    36  			},
    37  			"node_type": &schema.Schema{
    38  				Type:     schema.TypeString,
    39  				Required: true,
    40  				ForceNew: true,
    41  			},
    42  			"num_cache_nodes": &schema.Schema{
    43  				Type:     schema.TypeInt,
    44  				Required: true,
    45  				ForceNew: true,
    46  			},
    47  			"parameter_group_name": &schema.Schema{
    48  				Type:     schema.TypeString,
    49  				Optional: true,
    50  				Computed: true,
    51  				ForceNew: true,
    52  			},
    53  			"port": &schema.Schema{
    54  				Type:     schema.TypeInt,
    55  				Default:  11211,
    56  				Optional: true,
    57  				ForceNew: true,
    58  			},
    59  			"engine_version": &schema.Schema{
    60  				Type:     schema.TypeString,
    61  				Optional: true,
    62  				Computed: true,
    63  				ForceNew: true,
    64  			},
    65  			"subnet_group_name": &schema.Schema{
    66  				Type:     schema.TypeString,
    67  				Optional: true,
    68  				Computed: true,
    69  				ForceNew: true,
    70  			},
    71  			"security_group_names": &schema.Schema{
    72  				Type:     schema.TypeSet,
    73  				Optional: true,
    74  				Computed: true,
    75  				ForceNew: true,
    76  				Elem:     &schema.Schema{Type: schema.TypeString},
    77  				Set: func(v interface{}) int {
    78  					return hashcode.String(v.(string))
    79  				},
    80  			},
    81  			"security_group_ids": &schema.Schema{
    82  				Type:     schema.TypeSet,
    83  				Optional: true,
    84  				Computed: true,
    85  				ForceNew: true,
    86  				Elem:     &schema.Schema{Type: schema.TypeString},
    87  				Set: func(v interface{}) int {
    88  					return hashcode.String(v.(string))
    89  				},
    90  			},
    91  			// Exported Attributes
    92  			"cache_nodes": &schema.Schema{
    93  				Type:     schema.TypeList,
    94  				Computed: true,
    95  				Elem: &schema.Resource{
    96  					Schema: map[string]*schema.Schema{
    97  						"id": &schema.Schema{
    98  							Type:     schema.TypeString,
    99  							Computed: true,
   100  						},
   101  						"address": &schema.Schema{
   102  							Type:     schema.TypeString,
   103  							Computed: true,
   104  						},
   105  						"port": &schema.Schema{
   106  							Type:     schema.TypeInt,
   107  							Computed: true,
   108  						},
   109  					},
   110  				},
   111  			},
   112  
   113  			"tags": tagsSchema(),
   114  		},
   115  	}
   116  }
   117  
   118  func resourceAwsElasticacheClusterCreate(d *schema.ResourceData, meta interface{}) error {
   119  	conn := meta.(*AWSClient).elasticacheconn
   120  
   121  	clusterId := d.Get("cluster_id").(string)
   122  	nodeType := d.Get("node_type").(string)           // e.g) cache.m1.small
   123  	numNodes := int64(d.Get("num_cache_nodes").(int)) // 2
   124  	engine := d.Get("engine").(string)                // memcached
   125  	engineVersion := d.Get("engine_version").(string) // 1.4.14
   126  	port := int64(d.Get("port").(int))                // 11211
   127  	subnetGroupName := d.Get("subnet_group_name").(string)
   128  	securityNameSet := d.Get("security_group_names").(*schema.Set)
   129  	securityIdSet := d.Get("security_group_ids").(*schema.Set)
   130  
   131  	securityNames := expandStringList(securityNameSet.List())
   132  	securityIds := expandStringList(securityIdSet.List())
   133  
   134  	tags := tagsFromMapEC(d.Get("tags").(map[string]interface{}))
   135  	req := &elasticache.CreateCacheClusterInput{
   136  		CacheClusterID:          aws.String(clusterId),
   137  		CacheNodeType:           aws.String(nodeType),
   138  		NumCacheNodes:           aws.Long(numNodes),
   139  		Engine:                  aws.String(engine),
   140  		EngineVersion:           aws.String(engineVersion),
   141  		Port:                    aws.Long(port),
   142  		CacheSubnetGroupName:    aws.String(subnetGroupName),
   143  		CacheSecurityGroupNames: securityNames,
   144  		SecurityGroupIDs:        securityIds,
   145  		Tags:                    tags,
   146  	}
   147  
   148  	// parameter groups are optional and can be defaulted by AWS
   149  	if v, ok := d.GetOk("parameter_group_name"); ok {
   150  		req.CacheParameterGroupName = aws.String(v.(string))
   151  	}
   152  
   153  	_, err := conn.CreateCacheCluster(req)
   154  	if err != nil {
   155  		return fmt.Errorf("Error creating Elasticache: %s", err)
   156  	}
   157  
   158  	pending := []string{"creating"}
   159  	stateConf := &resource.StateChangeConf{
   160  		Pending:    pending,
   161  		Target:     "available",
   162  		Refresh:    CacheClusterStateRefreshFunc(conn, d.Id(), "available", pending),
   163  		Timeout:    10 * time.Minute,
   164  		Delay:      10 * time.Second,
   165  		MinTimeout: 3 * time.Second,
   166  	}
   167  
   168  	log.Printf("[DEBUG] Waiting for state to become available: %v", d.Id())
   169  	_, sterr := stateConf.WaitForState()
   170  	if sterr != nil {
   171  		return fmt.Errorf("Error waiting for elasticache (%s) to be created: %s", d.Id(), sterr)
   172  	}
   173  
   174  	d.SetId(clusterId)
   175  
   176  	return resourceAwsElasticacheClusterRead(d, meta)
   177  }
   178  
   179  func resourceAwsElasticacheClusterRead(d *schema.ResourceData, meta interface{}) error {
   180  	conn := meta.(*AWSClient).elasticacheconn
   181  	req := &elasticache.DescribeCacheClustersInput{
   182  		CacheClusterID:    aws.String(d.Id()),
   183  		ShowCacheNodeInfo: aws.Boolean(true),
   184  	}
   185  
   186  	res, err := conn.DescribeCacheClusters(req)
   187  	if err != nil {
   188  		return err
   189  	}
   190  
   191  	if len(res.CacheClusters) == 1 {
   192  		c := res.CacheClusters[0]
   193  		d.Set("cluster_id", c.CacheClusterID)
   194  		d.Set("node_type", c.CacheNodeType)
   195  		d.Set("num_cache_nodes", c.NumCacheNodes)
   196  		d.Set("engine", c.Engine)
   197  		d.Set("engine_version", c.EngineVersion)
   198  		if c.ConfigurationEndpoint != nil {
   199  			d.Set("port", c.ConfigurationEndpoint.Port)
   200  		}
   201  		d.Set("subnet_group_name", c.CacheSubnetGroupName)
   202  		d.Set("security_group_names", c.CacheSecurityGroups)
   203  		d.Set("security_group_ids", c.SecurityGroups)
   204  		d.Set("parameter_group_name", c.CacheParameterGroup)
   205  
   206  		if err := setCacheNodeData(d, c); err != nil {
   207  			return err
   208  		}
   209  		// list tags for resource
   210  		// set tags
   211  		arn, err := buildECARN(d, meta)
   212  		if err != nil {
   213  			log.Printf("[DEBUG] Error building ARN for ElastiCache Cluster, not setting Tags for cluster %s", *c.CacheClusterID)
   214  		} else {
   215  			resp, err := conn.ListTagsForResource(&elasticache.ListTagsForResourceInput{
   216  				ResourceName: aws.String(arn),
   217  			})
   218  
   219  			if err != nil {
   220  				log.Printf("[DEBUG] Error retreiving tags for ARN: %s", arn)
   221  			}
   222  
   223  			var et []*elasticache.Tag
   224  			if len(resp.TagList) > 0 {
   225  				et = resp.TagList
   226  			}
   227  			d.Set("tags", tagsToMapEC(et))
   228  		}
   229  	}
   230  
   231  	return nil
   232  }
   233  
   234  func resourceAwsElasticacheClusterUpdate(d *schema.ResourceData, meta interface{}) error {
   235  	conn := meta.(*AWSClient).elasticacheconn
   236  	arn, err := buildECARN(d, meta)
   237  	if err != nil {
   238  		log.Printf("[DEBUG] Error building ARN for ElastiCache Cluster, not updating Tags for cluster %s", d.Id())
   239  	} else {
   240  		if err := setTagsEC(conn, d, arn); err != nil {
   241  			return err
   242  		}
   243  	}
   244  	return resourceAwsElasticacheClusterRead(d, meta)
   245  }
   246  
   247  func setCacheNodeData(d *schema.ResourceData, c *elasticache.CacheCluster) error {
   248  	sortedCacheNodes := make([]*elasticache.CacheNode, len(c.CacheNodes))
   249  	copy(sortedCacheNodes, c.CacheNodes)
   250  	sort.Sort(byCacheNodeId(sortedCacheNodes))
   251  
   252  	cacheNodeData := make([]map[string]interface{}, 0, len(sortedCacheNodes))
   253  
   254  	for _, node := range sortedCacheNodes {
   255  		if node.CacheNodeID == nil || node.Endpoint == nil || node.Endpoint.Address == nil || node.Endpoint.Port == nil {
   256  			return fmt.Errorf("Unexpected nil pointer in: %#v", node)
   257  		}
   258  		cacheNodeData = append(cacheNodeData, map[string]interface{}{
   259  			"id":      *node.CacheNodeID,
   260  			"address": *node.Endpoint.Address,
   261  			"port":    int(*node.Endpoint.Port),
   262  		})
   263  	}
   264  
   265  	return d.Set("cache_nodes", cacheNodeData)
   266  }
   267  
   268  type byCacheNodeId []*elasticache.CacheNode
   269  
   270  func (b byCacheNodeId) Len() int      { return len(b) }
   271  func (b byCacheNodeId) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
   272  func (b byCacheNodeId) Less(i, j int) bool {
   273  	return b[i].CacheNodeID != nil && b[j].CacheNodeID != nil &&
   274  		*b[i].CacheNodeID < *b[j].CacheNodeID
   275  }
   276  
   277  func resourceAwsElasticacheClusterDelete(d *schema.ResourceData, meta interface{}) error {
   278  	conn := meta.(*AWSClient).elasticacheconn
   279  
   280  	req := &elasticache.DeleteCacheClusterInput{
   281  		CacheClusterID: aws.String(d.Id()),
   282  	}
   283  	_, err := conn.DeleteCacheCluster(req)
   284  	if err != nil {
   285  		return err
   286  	}
   287  
   288  	log.Printf("[DEBUG] Waiting for deletion: %v", d.Id())
   289  	stateConf := &resource.StateChangeConf{
   290  		Pending:    []string{"creating", "available", "deleting", "incompatible-parameters", "incompatible-network", "restore-failed"},
   291  		Target:     "",
   292  		Refresh:    CacheClusterStateRefreshFunc(conn, d.Id(), "", []string{}),
   293  		Timeout:    10 * time.Minute,
   294  		Delay:      10 * time.Second,
   295  		MinTimeout: 3 * time.Second,
   296  	}
   297  
   298  	_, sterr := stateConf.WaitForState()
   299  	if sterr != nil {
   300  		return fmt.Errorf("Error waiting for elasticache (%s) to delete: %s", d.Id(), sterr)
   301  	}
   302  
   303  	d.SetId("")
   304  
   305  	return nil
   306  }
   307  
   308  func CacheClusterStateRefreshFunc(conn *elasticache.ElastiCache, clusterID, givenState string, pending []string) resource.StateRefreshFunc {
   309  	return func() (interface{}, string, error) {
   310  		resp, err := conn.DescribeCacheClusters(&elasticache.DescribeCacheClustersInput{
   311  			CacheClusterID: aws.String(clusterID),
   312  		})
   313  		if err != nil {
   314  			apierr := err.(awserr.Error)
   315  			log.Printf("[DEBUG] message: %v, code: %v", apierr.Message(), apierr.Code())
   316  			if apierr.Message() == fmt.Sprintf("CacheCluster not found: %v", clusterID) {
   317  				log.Printf("[DEBUG] Detect deletion")
   318  				return nil, "", nil
   319  			}
   320  
   321  			log.Printf("[ERROR] CacheClusterStateRefreshFunc: %s", err)
   322  			return nil, "", err
   323  		}
   324  
   325  		c := resp.CacheClusters[0]
   326  		log.Printf("[DEBUG] status: %v", *c.CacheClusterStatus)
   327  
   328  		// return the current state if it's in the pending array
   329  		for _, p := range pending {
   330  			s := *c.CacheClusterStatus
   331  			if p == s {
   332  				log.Printf("[DEBUG] Return with status: %v", *c.CacheClusterStatus)
   333  				return c, p, nil
   334  			}
   335  		}
   336  
   337  		// return given state if it's not in pending
   338  		if givenState != "" {
   339  			return c, givenState, nil
   340  		}
   341  		log.Printf("[DEBUG] current status: %v", *c.CacheClusterStatus)
   342  		return c, *c.CacheClusterStatus, nil
   343  	}
   344  }
   345  
   346  func buildECARN(d *schema.ResourceData, meta interface{}) (string, error) {
   347  	iamconn := meta.(*AWSClient).iamconn
   348  	region := meta.(*AWSClient).region
   349  	// An zero value GetUserInput{} defers to the currently logged in user
   350  	resp, err := iamconn.GetUser(&iam.GetUserInput{})
   351  	if err != nil {
   352  		return "", err
   353  	}
   354  	userARN := *resp.User.ARN
   355  	accountID := strings.Split(userARN, ":")[4]
   356  	arn := fmt.Sprintf("arn:aws:elasticache:%s:%s:cluster:%s", region, accountID, d.Id())
   357  	return arn, nil
   358  }