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 }