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