github.com/ricardclau/terraform@v0.6.17-0.20160519222547-283e3ae6b5a9/builtin/providers/aws/resource_aws_rds_cluster.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/rds" 13 "github.com/hashicorp/terraform/helper/resource" 14 "github.com/hashicorp/terraform/helper/schema" 15 ) 16 17 func resourceAwsRDSCluster() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceAwsRDSClusterCreate, 20 Read: resourceAwsRDSClusterRead, 21 Update: resourceAwsRDSClusterUpdate, 22 Delete: resourceAwsRDSClusterDelete, 23 24 Schema: map[string]*schema.Schema{ 25 26 "availability_zones": &schema.Schema{ 27 Type: schema.TypeSet, 28 Elem: &schema.Schema{Type: schema.TypeString}, 29 Optional: true, 30 ForceNew: true, 31 Computed: true, 32 Set: schema.HashString, 33 }, 34 35 "cluster_identifier": &schema.Schema{ 36 Type: schema.TypeString, 37 Required: true, 38 ForceNew: true, 39 ValidateFunc: validateRdsId, 40 }, 41 42 "cluster_members": &schema.Schema{ 43 Type: schema.TypeSet, 44 Elem: &schema.Schema{Type: schema.TypeString}, 45 Optional: true, 46 Computed: true, 47 Set: schema.HashString, 48 }, 49 50 "database_name": &schema.Schema{ 51 Type: schema.TypeString, 52 Optional: true, 53 Computed: true, 54 ForceNew: true, 55 }, 56 57 "db_subnet_group_name": &schema.Schema{ 58 Type: schema.TypeString, 59 Optional: true, 60 ForceNew: true, 61 Computed: true, 62 }, 63 64 "parameter_group_name": &schema.Schema{ 65 Type: schema.TypeString, 66 Optional: true, 67 Computed: true, 68 }, 69 70 "endpoint": &schema.Schema{ 71 Type: schema.TypeString, 72 Computed: true, 73 }, 74 75 "engine": &schema.Schema{ 76 Type: schema.TypeString, 77 Computed: true, 78 }, 79 80 "storage_encrypted": &schema.Schema{ 81 Type: schema.TypeBool, 82 Optional: true, 83 Default: false, 84 ForceNew: true, 85 }, 86 87 "final_snapshot_identifier": &schema.Schema{ 88 Type: schema.TypeString, 89 Optional: true, 90 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 91 value := v.(string) 92 if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { 93 es = append(es, fmt.Errorf( 94 "only alphanumeric characters and hyphens allowed in %q", k)) 95 } 96 if regexp.MustCompile(`--`).MatchString(value) { 97 es = append(es, fmt.Errorf("%q cannot contain two consecutive hyphens", k)) 98 } 99 if regexp.MustCompile(`-$`).MatchString(value) { 100 es = append(es, fmt.Errorf("%q cannot end in a hyphen", k)) 101 } 102 return 103 }, 104 }, 105 106 "master_username": &schema.Schema{ 107 Type: schema.TypeString, 108 Required: true, 109 ForceNew: true, 110 }, 111 112 "master_password": &schema.Schema{ 113 Type: schema.TypeString, 114 Required: true, 115 }, 116 117 "port": &schema.Schema{ 118 Type: schema.TypeInt, 119 Optional: true, 120 Computed: true, 121 }, 122 123 // apply_immediately is used to determine when the update modifications 124 // take place. 125 // See http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.DBInstance.Modifying.html 126 "apply_immediately": &schema.Schema{ 127 Type: schema.TypeBool, 128 Optional: true, 129 Computed: true, 130 }, 131 132 "vpc_security_group_ids": &schema.Schema{ 133 Type: schema.TypeSet, 134 Optional: true, 135 Computed: true, 136 Elem: &schema.Schema{Type: schema.TypeString}, 137 Set: schema.HashString, 138 }, 139 140 "preferred_backup_window": &schema.Schema{ 141 Type: schema.TypeString, 142 Optional: true, 143 Computed: true, 144 }, 145 146 "preferred_maintenance_window": &schema.Schema{ 147 Type: schema.TypeString, 148 Optional: true, 149 Computed: true, 150 StateFunc: func(val interface{}) string { 151 if val == nil { 152 return "" 153 } 154 return strings.ToLower(val.(string)) 155 }, 156 }, 157 158 "backup_retention_period": &schema.Schema{ 159 Type: schema.TypeInt, 160 Optional: true, 161 Default: 1, 162 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 163 value := v.(int) 164 if value > 35 { 165 es = append(es, fmt.Errorf( 166 "backup retention period cannot be more than 35 days")) 167 } 168 return 169 }, 170 }, 171 }, 172 } 173 } 174 175 func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error { 176 conn := meta.(*AWSClient).rdsconn 177 178 createOpts := &rds.CreateDBClusterInput{ 179 DBClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)), 180 Engine: aws.String("aurora"), 181 MasterUserPassword: aws.String(d.Get("master_password").(string)), 182 MasterUsername: aws.String(d.Get("master_username").(string)), 183 StorageEncrypted: aws.Bool(d.Get("storage_encrypted").(bool)), 184 } 185 186 if v := d.Get("database_name"); v.(string) != "" { 187 createOpts.DatabaseName = aws.String(v.(string)) 188 } 189 190 if attr, ok := d.GetOk("port"); ok { 191 createOpts.Port = aws.Int64(int64(attr.(int))) 192 } 193 194 if attr, ok := d.GetOk("db_subnet_group_name"); ok { 195 createOpts.DBSubnetGroupName = aws.String(attr.(string)) 196 } 197 198 if attr, ok := d.GetOk("parameter_group_name"); ok { 199 createOpts.DBClusterParameterGroupName = aws.String(attr.(string)) 200 } 201 202 if attr := d.Get("vpc_security_group_ids").(*schema.Set); attr.Len() > 0 { 203 createOpts.VpcSecurityGroupIds = expandStringList(attr.List()) 204 } 205 206 if attr := d.Get("availability_zones").(*schema.Set); attr.Len() > 0 { 207 createOpts.AvailabilityZones = expandStringList(attr.List()) 208 } 209 210 if v, ok := d.GetOk("backup_retention_period"); ok { 211 createOpts.BackupRetentionPeriod = aws.Int64(int64(v.(int))) 212 } 213 214 if v, ok := d.GetOk("preferred_backup_window"); ok { 215 createOpts.PreferredBackupWindow = aws.String(v.(string)) 216 } 217 218 if v, ok := d.GetOk("preferred_maintenance_window"); ok { 219 createOpts.PreferredMaintenanceWindow = aws.String(v.(string)) 220 } 221 222 log.Printf("[DEBUG] RDS Cluster create options: %s", createOpts) 223 resp, err := conn.CreateDBCluster(createOpts) 224 if err != nil { 225 log.Printf("[ERROR] Error creating RDS Cluster: %s", err) 226 return err 227 } 228 229 log.Printf("[DEBUG]: Cluster create response: %s", resp) 230 d.SetId(*resp.DBCluster.DBClusterIdentifier) 231 stateConf := &resource.StateChangeConf{ 232 Pending: []string{"creating", "backing-up", "modifying"}, 233 Target: []string{"available"}, 234 Refresh: resourceAwsRDSClusterStateRefreshFunc(d, meta), 235 Timeout: 5 * time.Minute, 236 MinTimeout: 3 * time.Second, 237 } 238 239 // Wait, catching any errors 240 _, err = stateConf.WaitForState() 241 if err != nil { 242 return fmt.Errorf("[WARN] Error waiting for RDS Cluster state to be \"available\": %s", err) 243 } 244 245 return resourceAwsRDSClusterRead(d, meta) 246 } 247 248 func resourceAwsRDSClusterRead(d *schema.ResourceData, meta interface{}) error { 249 conn := meta.(*AWSClient).rdsconn 250 251 resp, err := conn.DescribeDBClusters(&rds.DescribeDBClustersInput{ 252 DBClusterIdentifier: aws.String(d.Id()), 253 }) 254 255 if err != nil { 256 if awsErr, ok := err.(awserr.Error); ok { 257 if "DBClusterNotFoundFault" == awsErr.Code() { 258 d.SetId("") 259 log.Printf("[DEBUG] RDS Cluster (%s) not found", d.Id()) 260 return nil 261 } 262 } 263 log.Printf("[DEBUG] Error describing RDS Cluster (%s)", d.Id()) 264 return err 265 } 266 267 var dbc *rds.DBCluster 268 for _, c := range resp.DBClusters { 269 if *c.DBClusterIdentifier == d.Id() { 270 dbc = c 271 } 272 } 273 274 if dbc == nil { 275 log.Printf("[WARN] RDS Cluster (%s) not found", d.Id()) 276 d.SetId("") 277 return nil 278 } 279 280 if err := d.Set("availability_zones", aws.StringValueSlice(dbc.AvailabilityZones)); err != nil { 281 return fmt.Errorf("[DEBUG] Error saving AvailabilityZones to state for RDS Cluster (%s): %s", d.Id(), err) 282 } 283 284 // Only set the DatabaseName if it is not nil. There is a known API bug where 285 // RDS accepts a DatabaseName but does not return it, causing a perpetual 286 // diff. 287 // See https://github.com/hashicorp/terraform/issues/4671 for backstory 288 if dbc.DatabaseName != nil { 289 d.Set("database_name", dbc.DatabaseName) 290 } 291 292 d.Set("db_subnet_group_name", dbc.DBSubnetGroup) 293 d.Set("parameter_group_name", dbc.DBClusterParameterGroup) 294 d.Set("endpoint", dbc.Endpoint) 295 d.Set("engine", dbc.Engine) 296 d.Set("master_username", dbc.MasterUsername) 297 d.Set("port", dbc.Port) 298 d.Set("storage_encrypted", dbc.StorageEncrypted) 299 d.Set("backup_retention_period", dbc.BackupRetentionPeriod) 300 d.Set("preferred_backup_window", dbc.PreferredBackupWindow) 301 d.Set("preferred_maintenance_window", dbc.PreferredMaintenanceWindow) 302 303 var vpcg []string 304 for _, g := range dbc.VpcSecurityGroups { 305 vpcg = append(vpcg, *g.VpcSecurityGroupId) 306 } 307 if err := d.Set("vpc_security_group_ids", vpcg); err != nil { 308 return fmt.Errorf("[DEBUG] Error saving VPC Security Group IDs to state for RDS Cluster (%s): %s", d.Id(), err) 309 } 310 311 var cm []string 312 for _, m := range dbc.DBClusterMembers { 313 cm = append(cm, *m.DBInstanceIdentifier) 314 } 315 if err := d.Set("cluster_members", cm); err != nil { 316 return fmt.Errorf("[DEBUG] Error saving RDS Cluster Members to state for RDS Cluster (%s): %s", d.Id(), err) 317 } 318 319 return nil 320 } 321 322 func resourceAwsRDSClusterUpdate(d *schema.ResourceData, meta interface{}) error { 323 conn := meta.(*AWSClient).rdsconn 324 325 req := &rds.ModifyDBClusterInput{ 326 ApplyImmediately: aws.Bool(d.Get("apply_immediately").(bool)), 327 DBClusterIdentifier: aws.String(d.Id()), 328 } 329 330 if d.HasChange("master_password") { 331 req.MasterUserPassword = aws.String(d.Get("master_password").(string)) 332 } 333 334 if d.HasChange("vpc_security_group_ids") { 335 if attr := d.Get("vpc_security_group_ids").(*schema.Set); attr.Len() > 0 { 336 req.VpcSecurityGroupIds = expandStringList(attr.List()) 337 } else { 338 req.VpcSecurityGroupIds = []*string{} 339 } 340 } 341 342 if d.HasChange("preferred_backup_window") { 343 req.PreferredBackupWindow = aws.String(d.Get("preferred_backup_window").(string)) 344 } 345 346 if d.HasChange("preferred_maintenance_window") { 347 req.PreferredMaintenanceWindow = aws.String(d.Get("preferred_maintenance_window").(string)) 348 } 349 350 if d.HasChange("backup_retention_period") { 351 req.BackupRetentionPeriod = aws.Int64(int64(d.Get("backup_retention_period").(int))) 352 } 353 354 if d.HasChange("parameter_group_name") { 355 d.SetPartial("parameter_group_name") 356 req.DBClusterParameterGroupName = aws.String(d.Get("parameter_group_name").(string)) 357 } 358 359 _, err := conn.ModifyDBCluster(req) 360 if err != nil { 361 return fmt.Errorf("[WARN] Error modifying RDS Cluster (%s): %s", d.Id(), err) 362 } 363 364 return resourceAwsRDSClusterRead(d, meta) 365 } 366 367 func resourceAwsRDSClusterDelete(d *schema.ResourceData, meta interface{}) error { 368 conn := meta.(*AWSClient).rdsconn 369 log.Printf("[DEBUG] Destroying RDS Cluster (%s)", d.Id()) 370 371 deleteOpts := rds.DeleteDBClusterInput{ 372 DBClusterIdentifier: aws.String(d.Id()), 373 } 374 375 finalSnapshot := d.Get("final_snapshot_identifier").(string) 376 if finalSnapshot == "" { 377 deleteOpts.SkipFinalSnapshot = aws.Bool(true) 378 } else { 379 deleteOpts.FinalDBSnapshotIdentifier = aws.String(finalSnapshot) 380 deleteOpts.SkipFinalSnapshot = aws.Bool(false) 381 } 382 383 log.Printf("[DEBUG] RDS Cluster delete options: %s", deleteOpts) 384 _, err := conn.DeleteDBCluster(&deleteOpts) 385 386 stateConf := &resource.StateChangeConf{ 387 Pending: []string{"available", "deleting", "backing-up", "modifying"}, 388 Target: []string{"destroyed"}, 389 Refresh: resourceAwsRDSClusterStateRefreshFunc(d, meta), 390 Timeout: 5 * time.Minute, 391 MinTimeout: 3 * time.Second, 392 } 393 394 // Wait, catching any errors 395 _, err = stateConf.WaitForState() 396 if err != nil { 397 return fmt.Errorf("[WARN] Error deleting RDS Cluster (%s): %s", d.Id(), err) 398 } 399 400 return nil 401 } 402 403 func resourceAwsRDSClusterStateRefreshFunc( 404 d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { 405 return func() (interface{}, string, error) { 406 conn := meta.(*AWSClient).rdsconn 407 408 resp, err := conn.DescribeDBClusters(&rds.DescribeDBClustersInput{ 409 DBClusterIdentifier: aws.String(d.Id()), 410 }) 411 412 if err != nil { 413 if awsErr, ok := err.(awserr.Error); ok { 414 if "DBClusterNotFoundFault" == awsErr.Code() { 415 return 42, "destroyed", nil 416 } 417 } 418 log.Printf("[WARN] Error on retrieving DB Cluster (%s) when waiting: %s", d.Id(), err) 419 return nil, "", err 420 } 421 422 var dbc *rds.DBCluster 423 424 for _, c := range resp.DBClusters { 425 if *c.DBClusterIdentifier == d.Id() { 426 dbc = c 427 } 428 } 429 430 if dbc == nil { 431 return 42, "destroyed", nil 432 } 433 434 if dbc.Status != nil { 435 log.Printf("[DEBUG] DB Cluster status (%s): %s", d.Id(), *dbc.Status) 436 } 437 438 return dbc, *dbc.Status, nil 439 } 440 }