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