From 93a577880ba2cfea907d01c3ebe8061f4825277c Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Wed, 24 Jun 2015 15:56:59 -0500 Subject: [PATCH 1/3] provider/aws: Allow in-place updates for ElastiCache cluster --- .../aws/resource_aws_elasticache_cluster.go | 54 ++++++++++++++++--- .../aws/r/elasticache_cluster.html.markdown | 6 +++ 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/builtin/providers/aws/resource_aws_elasticache_cluster.go b/builtin/providers/aws/resource_aws_elasticache_cluster.go index 48b8db0dad..54406fdc3a 100644 --- a/builtin/providers/aws/resource_aws_elasticache_cluster.go +++ b/builtin/providers/aws/resource_aws_elasticache_cluster.go @@ -9,6 +9,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/awsutil" "github.com/aws/aws-sdk-go/service/elasticache" "github.com/aws/aws-sdk-go/service/iam" "github.com/hashicorp/terraform/helper/hashcode" @@ -32,7 +33,6 @@ func resourceAwsElasticacheCluster() *schema.Resource { "engine": &schema.Schema{ Type: schema.TypeString, Required: true, - ForceNew: true, }, "node_type": &schema.Schema{ Type: schema.TypeString, @@ -42,13 +42,11 @@ func resourceAwsElasticacheCluster() *schema.Resource { "num_cache_nodes": &schema.Schema{ Type: schema.TypeInt, Required: true, - ForceNew: true, }, "parameter_group_name": &schema.Schema{ Type: schema.TypeString, Optional: true, Computed: true, - ForceNew: true, }, "port": &schema.Schema{ Type: schema.TypeInt, @@ -59,7 +57,6 @@ func resourceAwsElasticacheCluster() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, - ForceNew: true, }, "subnet_group_name": &schema.Schema{ Type: schema.TypeString, @@ -81,7 +78,6 @@ func resourceAwsElasticacheCluster() *schema.Resource { Type: schema.TypeSet, Optional: true, Computed: true, - ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, Set: func(v interface{}) int { return hashcode.String(v.(string)) @@ -110,6 +106,15 @@ func resourceAwsElasticacheCluster() *schema.Resource { }, "tags": tagsSchema(), + + // apply_immediately is used to determine when the update modifications + // take place. + // See http://docs.aws.amazon.com/AmazonElastiCache/latest/APIReference/API_ModifyCacheCluster.html + "apply_immediately": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, }, } } @@ -240,6 +245,43 @@ func resourceAwsElasticacheClusterUpdate(d *schema.ResourceData, meta interface{ return err } } + + req := &elasticache.ModifyCacheClusterInput{ + CacheClusterID: aws.String(d.Id()), + ApplyImmediately: aws.Boolean(d.Get("apply_immediately").(bool)), + } + + requestUpdate := false + if d.HasChange("security_group_ids") { + if attr := d.Get("security_group_ids").(*schema.Set); attr.Len() > 0 { + req.SecurityGroupIDs = expandStringList(attr.List()) + requestUpdate = true + } + } + + if d.HasChange("parameter_group_name") { + req.CacheParameterGroupName = aws.String(d.Get("parameter_group_name").(string)) + requestUpdate = true + } + + if d.HasChange("engine_version") { + req.EngineVersion = aws.String(d.Get("engine_version").(string)) + requestUpdate = true + } + + if d.HasChange("num_cache_nodes") { + req.NumCacheNodes = aws.Long(int64(d.Get("num_cache_nodes").(int))) + requestUpdate = true + } + + if requestUpdate { + log.Printf("[DEBUG] Modifying ElastiCache Cluster (%s), opts:\n%s", d.Id(), awsutil.StringValue(req)) + _, err := conn.ModifyCacheCluster(req) + if err != nil { + return fmt.Errorf("[WARN] Error updating ElastiCache cluster (%s), error: %s", d.Id(), err) + } + } + return resourceAwsElasticacheClusterRead(d, meta) } @@ -252,7 +294,7 @@ func setCacheNodeData(d *schema.ResourceData, c *elasticache.CacheCluster) error for _, node := range sortedCacheNodes { if node.CacheNodeID == nil || node.Endpoint == nil || node.Endpoint.Address == nil || node.Endpoint.Port == nil { - return fmt.Errorf("Unexpected nil pointer in: %#v", node) + return fmt.Errorf("Unexpected nil pointer in: %s", awsutil.StringValue(node)) } cacheNodeData = append(cacheNodeData, map[string]interface{}{ "id": *node.CacheNodeID, diff --git a/website/source/docs/providers/aws/r/elasticache_cluster.html.markdown b/website/source/docs/providers/aws/r/elasticache_cluster.html.markdown index 809c18d07e..f830abcf26 100644 --- a/website/source/docs/providers/aws/r/elasticache_cluster.html.markdown +++ b/website/source/docs/providers/aws/r/elasticache_cluster.html.markdown @@ -60,6 +60,10 @@ names to associate with this cache cluster * `security_group_ids` – (Optional, VPC only) One or more VPC security groups associated with the cache cluster +* `apply_immediately` - (Optional) Specifies whether any database modifications + are applied immediately, or during the next maintenance window. Default is + `false`. See [Amazon ElastiCache Documentation for more information.][1] + * `tags` - (Optional) A mapping of tags to assign to the resource. @@ -69,3 +73,5 @@ The following attributes are exported: * `cache_nodes` - List of node objects including `id`, `address` and `port`. Referenceable e.g. as `${aws_elasticache_cluster.bar.cache_nodes.0.address}` + +[1]: http://docs.aws.amazon.com/AmazonElastiCache/latest/APIReference/API_ModifyCacheCluster.html From 54ddd55c637be32c00a0b07e676e7bdbb9492602 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Thu, 25 Jun 2015 10:45:36 -0500 Subject: [PATCH 2/3] add note on availability --- .../docs/providers/aws/r/elasticache_cluster.html.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/website/source/docs/providers/aws/r/elasticache_cluster.html.markdown b/website/source/docs/providers/aws/r/elasticache_cluster.html.markdown index f830abcf26..bbc52ac2cf 100644 --- a/website/source/docs/providers/aws/r/elasticache_cluster.html.markdown +++ b/website/source/docs/providers/aws/r/elasticache_cluster.html.markdown @@ -63,6 +63,7 @@ names to associate with this cache cluster * `apply_immediately` - (Optional) Specifies whether any database modifications are applied immediately, or during the next maintenance window. Default is `false`. See [Amazon ElastiCache Documentation for more information.][1] + (Available since v0.6.0) * `tags` - (Optional) A mapping of tags to assign to the resource. From 2e23210e58f88ef385f5ece7ef57673f886bb4b8 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Thu, 25 Jun 2015 11:01:51 -0500 Subject: [PATCH 3/3] poll ElastiCache cluster status on update --- .../aws/resource_aws_elasticache_cluster.go | 102 ++++++++++-------- 1 file changed, 59 insertions(+), 43 deletions(-) diff --git a/builtin/providers/aws/resource_aws_elasticache_cluster.go b/builtin/providers/aws/resource_aws_elasticache_cluster.go index 54406fdc3a..d47b275c7d 100644 --- a/builtin/providers/aws/resource_aws_elasticache_cluster.go +++ b/builtin/providers/aws/resource_aws_elasticache_cluster.go @@ -9,7 +9,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/aws/awsutil" + "github.com/aws/aws-sdk-go/aws/awsutil" "github.com/aws/aws-sdk-go/service/elasticache" "github.com/aws/aws-sdk-go/service/iam" "github.com/hashicorp/terraform/helper/hashcode" @@ -107,14 +107,14 @@ func resourceAwsElasticacheCluster() *schema.Resource { "tags": tagsSchema(), - // apply_immediately is used to determine when the update modifications - // take place. - // See http://docs.aws.amazon.com/AmazonElastiCache/latest/APIReference/API_ModifyCacheCluster.html - "apply_immediately": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - Computed: true, - }, + // apply_immediately is used to determine when the update modifications + // take place. + // See http://docs.aws.amazon.com/AmazonElastiCache/latest/APIReference/API_ModifyCacheCluster.html + "apply_immediately": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, }, } } @@ -163,7 +163,7 @@ func resourceAwsElasticacheClusterCreate(d *schema.ResourceData, meta interface{ stateConf := &resource.StateChangeConf{ Pending: pending, Target: "available", - Refresh: CacheClusterStateRefreshFunc(conn, d.Id(), "available", pending), + Refresh: cacheClusterStateRefreshFunc(conn, d.Id(), "available", pending), Timeout: 10 * time.Minute, Delay: 10 * time.Second, MinTimeout: 3 * time.Second, @@ -246,41 +246,57 @@ func resourceAwsElasticacheClusterUpdate(d *schema.ResourceData, meta interface{ } } - req := &elasticache.ModifyCacheClusterInput{ - CacheClusterID: aws.String(d.Id()), - ApplyImmediately: aws.Boolean(d.Get("apply_immediately").(bool)), - } + req := &elasticache.ModifyCacheClusterInput{ + CacheClusterID: aws.String(d.Id()), + ApplyImmediately: aws.Boolean(d.Get("apply_immediately").(bool)), + } - requestUpdate := false - if d.HasChange("security_group_ids") { - if attr := d.Get("security_group_ids").(*schema.Set); attr.Len() > 0 { - req.SecurityGroupIDs = expandStringList(attr.List()) - requestUpdate = true - } - } + requestUpdate := false + if d.HasChange("security_group_ids") { + if attr := d.Get("security_group_ids").(*schema.Set); attr.Len() > 0 { + req.SecurityGroupIDs = expandStringList(attr.List()) + requestUpdate = true + } + } - if d.HasChange("parameter_group_name") { - req.CacheParameterGroupName = aws.String(d.Get("parameter_group_name").(string)) - requestUpdate = true - } + if d.HasChange("parameter_group_name") { + req.CacheParameterGroupName = aws.String(d.Get("parameter_group_name").(string)) + requestUpdate = true + } - if d.HasChange("engine_version") { - req.EngineVersion = aws.String(d.Get("engine_version").(string)) - requestUpdate = true - } + if d.HasChange("engine_version") { + req.EngineVersion = aws.String(d.Get("engine_version").(string)) + requestUpdate = true + } - if d.HasChange("num_cache_nodes") { - req.NumCacheNodes = aws.Long(int64(d.Get("num_cache_nodes").(int))) - requestUpdate = true - } + if d.HasChange("num_cache_nodes") { + req.NumCacheNodes = aws.Long(int64(d.Get("num_cache_nodes").(int))) + requestUpdate = true + } - if requestUpdate { - log.Printf("[DEBUG] Modifying ElastiCache Cluster (%s), opts:\n%s", d.Id(), awsutil.StringValue(req)) - _, err := conn.ModifyCacheCluster(req) - if err != nil { - return fmt.Errorf("[WARN] Error updating ElastiCache cluster (%s), error: %s", d.Id(), err) - } - } + if requestUpdate { + log.Printf("[DEBUG] Modifying ElastiCache Cluster (%s), opts:\n%s", d.Id(), awsutil.StringValue(req)) + _, err := conn.ModifyCacheCluster(req) + if err != nil { + return fmt.Errorf("[WARN] Error updating ElastiCache cluster (%s), error: %s", d.Id(), err) + } + + log.Printf("[DEBUG] Waiting for update: %s", d.Id()) + pending := []string{"modifying", "rebooting cache cluster nodes", "snapshotting"} + stateConf := &resource.StateChangeConf{ + Pending: pending, + Target: "available", + Refresh: cacheClusterStateRefreshFunc(conn, d.Id(), "available", pending), + Timeout: 5 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, sterr := stateConf.WaitForState() + if sterr != nil { + return fmt.Errorf("Error waiting for elasticache (%s) to update: %s", d.Id(), sterr) + } + } return resourceAwsElasticacheClusterRead(d, meta) } @@ -294,7 +310,7 @@ func setCacheNodeData(d *schema.ResourceData, c *elasticache.CacheCluster) error for _, node := range sortedCacheNodes { if node.CacheNodeID == nil || node.Endpoint == nil || node.Endpoint.Address == nil || node.Endpoint.Port == nil { - return fmt.Errorf("Unexpected nil pointer in: %s", awsutil.StringValue(node)) + return fmt.Errorf("Unexpected nil pointer in: %s", awsutil.StringValue(node)) } cacheNodeData = append(cacheNodeData, map[string]interface{}{ "id": *node.CacheNodeID, @@ -330,7 +346,7 @@ func resourceAwsElasticacheClusterDelete(d *schema.ResourceData, meta interface{ stateConf := &resource.StateChangeConf{ Pending: []string{"creating", "available", "deleting", "incompatible-parameters", "incompatible-network", "restore-failed"}, Target: "", - Refresh: CacheClusterStateRefreshFunc(conn, d.Id(), "", []string{}), + Refresh: cacheClusterStateRefreshFunc(conn, d.Id(), "", []string{}), Timeout: 10 * time.Minute, Delay: 10 * time.Second, MinTimeout: 3 * time.Second, @@ -346,7 +362,7 @@ func resourceAwsElasticacheClusterDelete(d *schema.ResourceData, meta interface{ return nil } -func CacheClusterStateRefreshFunc(conn *elasticache.ElastiCache, clusterID, givenState string, pending []string) resource.StateRefreshFunc { +func cacheClusterStateRefreshFunc(conn *elasticache.ElastiCache, clusterID, givenState string, pending []string) resource.StateRefreshFunc { return func() (interface{}, string, error) { resp, err := conn.DescribeCacheClusters(&elasticache.DescribeCacheClustersInput{ CacheClusterID: aws.String(clusterID),