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 }