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