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