github.com/mohanarpit/terraform@v0.6.16-0.20160909104007-291f29853544/builtin/providers/aws/resource_aws_elasticache_replication_group.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"regexp"
     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/hashicorp/terraform/helper/resource"
    14  	"github.com/hashicorp/terraform/helper/schema"
    15  )
    16  
    17  func resourceAwsElasticacheReplicationGroup() *schema.Resource {
    18  
    19  	resourceSchema := resourceAwsElastiCacheCommonSchema()
    20  
    21  	resourceSchema["replication_group_id"] = &schema.Schema{
    22  		Type:         schema.TypeString,
    23  		Required:     true,
    24  		ForceNew:     true,
    25  		ValidateFunc: validateAwsElastiCacheReplicationGroupId,
    26  	}
    27  
    28  	resourceSchema["automatic_failover_enabled"] = &schema.Schema{
    29  		Type:     schema.TypeBool,
    30  		Optional: true,
    31  		Default:  false,
    32  	}
    33  
    34  	resourceSchema["replication_group_description"] = &schema.Schema{
    35  		Type:     schema.TypeString,
    36  		Required: true,
    37  	}
    38  
    39  	resourceSchema["number_cache_clusters"] = &schema.Schema{
    40  		Type:     schema.TypeInt,
    41  		Required: true,
    42  		ForceNew: true,
    43  	}
    44  
    45  	resourceSchema["primary_endpoint_address"] = &schema.Schema{
    46  		Type:     schema.TypeString,
    47  		Computed: true,
    48  	}
    49  
    50  	resourceSchema["engine"].Required = false
    51  	resourceSchema["engine"].Optional = true
    52  	resourceSchema["engine"].Default = "redis"
    53  	resourceSchema["engine"].ValidateFunc = validateAwsElastiCacheReplicationGroupEngine
    54  
    55  	return &schema.Resource{
    56  		Create: resourceAwsElasticacheReplicationGroupCreate,
    57  		Read:   resourceAwsElasticacheReplicationGroupRead,
    58  		Update: resourceAwsElasticacheReplicationGroupUpdate,
    59  		Delete: resourceAwsElasticacheReplicationGroupDelete,
    60  
    61  		Schema: resourceSchema,
    62  	}
    63  }
    64  
    65  func resourceAwsElasticacheReplicationGroupCreate(d *schema.ResourceData, meta interface{}) error {
    66  	conn := meta.(*AWSClient).elasticacheconn
    67  
    68  	tags := tagsFromMapEC(d.Get("tags").(map[string]interface{}))
    69  	params := &elasticache.CreateReplicationGroupInput{
    70  		ReplicationGroupId:          aws.String(d.Get("replication_group_id").(string)),
    71  		ReplicationGroupDescription: aws.String(d.Get("replication_group_description").(string)),
    72  		AutomaticFailoverEnabled:    aws.Bool(d.Get("automatic_failover_enabled").(bool)),
    73  		CacheNodeType:               aws.String(d.Get("node_type").(string)),
    74  		Engine:                      aws.String(d.Get("engine").(string)),
    75  		Port:                        aws.Int64(int64(d.Get("port").(int))),
    76  		NumCacheClusters:            aws.Int64(int64(d.Get("number_cache_clusters").(int))),
    77  		Tags:                        tags,
    78  	}
    79  
    80  	if v, ok := d.GetOk("engine_version"); ok {
    81  		params.EngineVersion = aws.String(v.(string))
    82  	}
    83  
    84  	preferred_azs := d.Get("availability_zones").(*schema.Set).List()
    85  	if len(preferred_azs) > 0 {
    86  		azs := expandStringList(preferred_azs)
    87  		params.PreferredCacheClusterAZs = azs
    88  	}
    89  
    90  	if v, ok := d.GetOk("parameter_group_name"); ok {
    91  		params.CacheParameterGroupName = aws.String(v.(string))
    92  	}
    93  
    94  	if v, ok := d.GetOk("subnet_group_name"); ok {
    95  		params.CacheSubnetGroupName = aws.String(v.(string))
    96  	}
    97  
    98  	security_group_names := d.Get("security_group_names").(*schema.Set).List()
    99  	if len(security_group_names) > 0 {
   100  		params.CacheSecurityGroupNames = expandStringList(security_group_names)
   101  	}
   102  
   103  	security_group_ids := d.Get("security_group_ids").(*schema.Set).List()
   104  	if len(security_group_ids) > 0 {
   105  		params.SecurityGroupIds = expandStringList(security_group_ids)
   106  	}
   107  
   108  	snaps := d.Get("snapshot_arns").(*schema.Set).List()
   109  	if len(snaps) > 0 {
   110  		params.SnapshotArns = expandStringList(snaps)
   111  	}
   112  
   113  	if v, ok := d.GetOk("maintenance_window"); ok {
   114  		params.PreferredMaintenanceWindow = aws.String(v.(string))
   115  	}
   116  
   117  	if v, ok := d.GetOk("notification_topic_arn"); ok {
   118  		params.NotificationTopicArn = aws.String(v.(string))
   119  	}
   120  
   121  	if v, ok := d.GetOk("snapshot_retention_limit"); ok {
   122  		params.SnapshotRetentionLimit = aws.Int64(int64(v.(int)))
   123  	}
   124  
   125  	if v, ok := d.GetOk("snapshot_window"); ok {
   126  		params.SnapshotWindow = aws.String(v.(string))
   127  	}
   128  
   129  	if v, ok := d.GetOk("snapshot_name"); ok {
   130  		params.SnapshotName = aws.String(v.(string))
   131  	}
   132  
   133  	resp, err := conn.CreateReplicationGroup(params)
   134  	if err != nil {
   135  		return fmt.Errorf("Error creating Elasticache Replication Group: %s", err)
   136  	}
   137  
   138  	d.SetId(*resp.ReplicationGroup.ReplicationGroupId)
   139  
   140  	pending := []string{"creating", "modifying", "restoring"}
   141  	stateConf := &resource.StateChangeConf{
   142  		Pending:    pending,
   143  		Target:     []string{"available"},
   144  		Refresh:    cacheReplicationGroupStateRefreshFunc(conn, d.Id(), "available", pending),
   145  		Timeout:    40 * time.Minute,
   146  		MinTimeout: 10 * time.Second,
   147  		Delay:      30 * time.Second,
   148  	}
   149  
   150  	log.Printf("[DEBUG] Waiting for state to become available: %v", d.Id())
   151  	_, sterr := stateConf.WaitForState()
   152  	if sterr != nil {
   153  		return fmt.Errorf("Error waiting for elasticache replication group (%s) to be created: %s", d.Id(), sterr)
   154  	}
   155  
   156  	return resourceAwsElasticacheReplicationGroupRead(d, meta)
   157  }
   158  
   159  func resourceAwsElasticacheReplicationGroupRead(d *schema.ResourceData, meta interface{}) error {
   160  	conn := meta.(*AWSClient).elasticacheconn
   161  	req := &elasticache.DescribeReplicationGroupsInput{
   162  		ReplicationGroupId: aws.String(d.Id()),
   163  	}
   164  
   165  	res, err := conn.DescribeReplicationGroups(req)
   166  	if err != nil {
   167  		if eccErr, ok := err.(awserr.Error); ok && eccErr.Code() == "ReplicationGroupNotFoundFault" {
   168  			log.Printf("[WARN] Elasticache Replication Group (%s) not found", d.Id())
   169  			d.SetId("")
   170  			return nil
   171  		}
   172  
   173  		return err
   174  	}
   175  
   176  	var rgp *elasticache.ReplicationGroup
   177  	for _, r := range res.ReplicationGroups {
   178  		if *r.ReplicationGroupId == d.Id() {
   179  			rgp = r
   180  		}
   181  	}
   182  
   183  	if rgp == nil {
   184  		log.Printf("[WARN] Replication Group (%s) not found", d.Id())
   185  		d.SetId("")
   186  		return nil
   187  	}
   188  
   189  	if *rgp.Status == "deleting" {
   190  		log.Printf("[WARN] The Replication Group %q is currently in the `deleting` state", d.Id())
   191  		d.SetId("")
   192  		return nil
   193  	}
   194  
   195  	d.Set("automatic_failover_enabled", rgp.AutomaticFailover)
   196  	d.Set("replication_group_description", rgp.Description)
   197  	d.Set("number_cache_clusters", len(rgp.MemberClusters))
   198  	d.Set("replication_group_id", rgp.ReplicationGroupId)
   199  
   200  	if rgp.NodeGroups != nil {
   201  		cacheCluster := *rgp.NodeGroups[0].NodeGroupMembers[0]
   202  
   203  		res, err := conn.DescribeCacheClusters(&elasticache.DescribeCacheClustersInput{
   204  			CacheClusterId:    cacheCluster.CacheClusterId,
   205  			ShowCacheNodeInfo: aws.Bool(true),
   206  		})
   207  		if err != nil {
   208  			return err
   209  		}
   210  
   211  		if len(res.CacheClusters) == 0 {
   212  			return nil
   213  		}
   214  
   215  		c := res.CacheClusters[0]
   216  		d.Set("node_type", c.CacheNodeType)
   217  		d.Set("engine", c.Engine)
   218  		d.Set("engine_version", c.EngineVersion)
   219  		d.Set("subnet_group_name", c.CacheSubnetGroupName)
   220  		d.Set("security_group_names", c.CacheSecurityGroups)
   221  		d.Set("security_group_ids", c.SecurityGroups)
   222  		d.Set("parameter_group_name", c.CacheParameterGroup)
   223  		d.Set("maintenance_window", c.PreferredMaintenanceWindow)
   224  		d.Set("snapshot_window", c.SnapshotWindow)
   225  		d.Set("snapshot_retention_limit", c.SnapshotRetentionLimit)
   226  
   227  		d.Set("primary_endpoint_address", rgp.NodeGroups[0].PrimaryEndpoint.Address)
   228  
   229  	}
   230  
   231  	return nil
   232  }
   233  
   234  func resourceAwsElasticacheReplicationGroupUpdate(d *schema.ResourceData, meta interface{}) error {
   235  	conn := meta.(*AWSClient).elasticacheconn
   236  
   237  	requestUpdate := false
   238  	params := &elasticache.ModifyReplicationGroupInput{
   239  		ApplyImmediately:   aws.Bool(d.Get("apply_immediately").(bool)),
   240  		ReplicationGroupId: aws.String(d.Id()),
   241  	}
   242  
   243  	if d.HasChange("replication_group_description") {
   244  		params.ReplicationGroupDescription = aws.String(d.Get("replication_group_description").(string))
   245  		requestUpdate = true
   246  	}
   247  
   248  	if d.HasChange("automatic_failover_enabled") {
   249  		params.AutomaticFailoverEnabled = aws.Bool(d.Get("automatic_failover_enabled").(bool))
   250  		requestUpdate = true
   251  	}
   252  
   253  	if d.HasChange("security_group_ids") {
   254  		if attr := d.Get("security_group_ids").(*schema.Set); attr.Len() > 0 {
   255  			params.SecurityGroupIds = expandStringList(attr.List())
   256  			requestUpdate = true
   257  		}
   258  	}
   259  
   260  	if d.HasChange("security_group_names") {
   261  		if attr := d.Get("security_group_names").(*schema.Set); attr.Len() > 0 {
   262  			params.CacheSecurityGroupNames = expandStringList(attr.List())
   263  			requestUpdate = true
   264  		}
   265  	}
   266  
   267  	if d.HasChange("preferred_maintenance_window") {
   268  		params.PreferredMaintenanceWindow = aws.String(d.Get("preferred_maintenance_window").(string))
   269  		requestUpdate = true
   270  	}
   271  
   272  	if d.HasChange("notification_topic_arn") {
   273  		params.NotificationTopicArn = aws.String(d.Get("notification_topic_arn").(string))
   274  		requestUpdate = true
   275  	}
   276  
   277  	if d.HasChange("parameter_group_name") {
   278  		params.CacheParameterGroupName = aws.String(d.Get("cache_parameter_group_name").(string))
   279  		requestUpdate = true
   280  	}
   281  
   282  	if d.HasChange("engine_version") {
   283  		params.EngineVersion = aws.String(d.Get("engine_version").(string))
   284  		requestUpdate = true
   285  	}
   286  
   287  	if d.HasChange("snapshot_retention_limit") {
   288  		params.SnapshotRetentionLimit = aws.Int64(int64(d.Get("snapshot_retention_limit").(int)))
   289  		requestUpdate = true
   290  	}
   291  
   292  	if d.HasChange("snapshot_window") {
   293  		params.SnapshotWindow = aws.String(d.Get("snapshot_window").(string))
   294  		requestUpdate = true
   295  	}
   296  
   297  	if d.HasChange("node_type") {
   298  		params.CacheNodeType = aws.String(d.Get("node_type").(string))
   299  		requestUpdate = true
   300  	}
   301  
   302  	if requestUpdate {
   303  		_, err := conn.ModifyReplicationGroup(params)
   304  		if err != nil {
   305  			return fmt.Errorf("Error updating Elasticache replication group: %s", err)
   306  		}
   307  
   308  		pending := []string{"creating", "modifying", "snapshotting"}
   309  		stateConf := &resource.StateChangeConf{
   310  			Pending:    pending,
   311  			Target:     []string{"available"},
   312  			Refresh:    cacheReplicationGroupStateRefreshFunc(conn, d.Id(), "available", pending),
   313  			Timeout:    40 * time.Minute,
   314  			MinTimeout: 10 * time.Second,
   315  			Delay:      30 * time.Second,
   316  		}
   317  
   318  		log.Printf("[DEBUG] Waiting for state to become available: %v", d.Id())
   319  		_, sterr := stateConf.WaitForState()
   320  		if sterr != nil {
   321  			return fmt.Errorf("Error waiting for elasticache replication group (%s) to be created: %s", d.Id(), sterr)
   322  		}
   323  	}
   324  	return resourceAwsElasticacheReplicationGroupRead(d, meta)
   325  }
   326  
   327  func resourceAwsElasticacheReplicationGroupDelete(d *schema.ResourceData, meta interface{}) error {
   328  	conn := meta.(*AWSClient).elasticacheconn
   329  
   330  	req := &elasticache.DeleteReplicationGroupInput{
   331  		ReplicationGroupId: aws.String(d.Id()),
   332  	}
   333  
   334  	_, err := conn.DeleteReplicationGroup(req)
   335  	if err != nil {
   336  		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "ReplicationGroupNotFoundFault" {
   337  			d.SetId("")
   338  			return nil
   339  		}
   340  
   341  		return fmt.Errorf("Error deleting Elasticache replication group: %s", err)
   342  	}
   343  
   344  	log.Printf("[DEBUG] Waiting for deletion: %v", d.Id())
   345  	stateConf := &resource.StateChangeConf{
   346  		Pending:    []string{"creating", "available", "deleting"},
   347  		Target:     []string{},
   348  		Refresh:    cacheReplicationGroupStateRefreshFunc(conn, d.Id(), "", []string{}),
   349  		Timeout:    40 * time.Minute,
   350  		MinTimeout: 10 * time.Second,
   351  		Delay:      30 * time.Second,
   352  	}
   353  
   354  	_, sterr := stateConf.WaitForState()
   355  	if sterr != nil {
   356  		return fmt.Errorf("Error waiting for replication group (%s) to delete: %s", d.Id(), sterr)
   357  	}
   358  
   359  	return nil
   360  }
   361  
   362  func cacheReplicationGroupStateRefreshFunc(conn *elasticache.ElastiCache, replicationGroupId, givenState string, pending []string) resource.StateRefreshFunc {
   363  	return func() (interface{}, string, error) {
   364  		resp, err := conn.DescribeReplicationGroups(&elasticache.DescribeReplicationGroupsInput{
   365  			ReplicationGroupId: aws.String(replicationGroupId),
   366  		})
   367  		if err != nil {
   368  			if eccErr, ok := err.(awserr.Error); ok && eccErr.Code() == "ReplicationGroupNotFoundFault" {
   369  				log.Printf("[DEBUG] Replication Group Not Found")
   370  				return nil, "", nil
   371  			}
   372  
   373  			log.Printf("[ERROR] cacheClusterReplicationGroupStateRefreshFunc: %s", err)
   374  			return nil, "", err
   375  		}
   376  
   377  		if len(resp.ReplicationGroups) == 0 {
   378  			return nil, "", fmt.Errorf("[WARN] Error: no Cache Replication Groups found for id (%s)", replicationGroupId)
   379  		}
   380  
   381  		var rg *elasticache.ReplicationGroup
   382  		for _, replicationGroup := range resp.ReplicationGroups {
   383  			if *replicationGroup.ReplicationGroupId == replicationGroupId {
   384  				log.Printf("[DEBUG] Found matching ElastiCache Replication Group: %s", *replicationGroup.ReplicationGroupId)
   385  				rg = replicationGroup
   386  			}
   387  		}
   388  
   389  		if rg == nil {
   390  			return nil, "", fmt.Errorf("[WARN] Error: no matching ElastiCache Replication Group for id (%s)", replicationGroupId)
   391  		}
   392  
   393  		log.Printf("[DEBUG] ElastiCache Replication Group (%s) status: %v", replicationGroupId, *rg.Status)
   394  
   395  		// return the current state if it's in the pending array
   396  		for _, p := range pending {
   397  			log.Printf("[DEBUG] ElastiCache: checking pending state (%s) for Replication Group (%s), Replication Group status: %s", pending, replicationGroupId, *rg.Status)
   398  			s := *rg.Status
   399  			if p == s {
   400  				log.Printf("[DEBUG] Return with status: %v", *rg.Status)
   401  				return s, p, nil
   402  			}
   403  		}
   404  
   405  		return rg, *rg.Status, nil
   406  	}
   407  }
   408  
   409  func validateAwsElastiCacheReplicationGroupEngine(v interface{}, k string) (ws []string, errors []error) {
   410  	if strings.ToLower(v.(string)) != "redis" {
   411  		errors = append(errors, fmt.Errorf("The only acceptable Engine type when using Replication Groups is Redis"))
   412  	}
   413  	return
   414  }
   415  
   416  func validateAwsElastiCacheReplicationGroupId(v interface{}, k string) (ws []string, errors []error) {
   417  	value := v.(string)
   418  	if (len(value) < 1) || (len(value) > 16) {
   419  		errors = append(errors, fmt.Errorf(
   420  			"%q must contain from 1 to 16 alphanumeric characters or hyphens", k))
   421  	}
   422  	if !regexp.MustCompile(`^[0-9a-zA-Z-]+$`).MatchString(value) {
   423  		errors = append(errors, fmt.Errorf(
   424  			"only alphanumeric characters and hyphens allowed in %q", k))
   425  	}
   426  	if !regexp.MustCompile(`^[a-z]`).MatchString(value) {
   427  		errors = append(errors, fmt.Errorf(
   428  			"first character of %q must be a letter", k))
   429  	}
   430  	if regexp.MustCompile(`--`).MatchString(value) {
   431  		errors = append(errors, fmt.Errorf(
   432  			"%q cannot contain two consecutive hyphens", k))
   433  	}
   434  	if regexp.MustCompile(`-$`).MatchString(value) {
   435  		errors = append(errors, fmt.Errorf(
   436  			"%q cannot end with a hyphen", k))
   437  	}
   438  	return
   439  }