2015-08-04 23:24:55 +02:00
package aws
import (
2015-10-07 18:27:24 +02:00
"fmt"
"log"
"regexp"
2015-11-04 20:40:22 +01:00
"strings"
2015-10-07 18:27:24 +02:00
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/rds"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
2015-08-04 23:24:55 +02:00
)
func resourceAwsRDSCluster ( ) * schema . Resource {
2015-10-07 18:27:24 +02:00
return & schema . Resource {
Create : resourceAwsRDSClusterCreate ,
Read : resourceAwsRDSClusterRead ,
Update : resourceAwsRDSClusterUpdate ,
Delete : resourceAwsRDSClusterDelete ,
Schema : map [ string ] * schema . Schema {
"availability_zones" : & schema . Schema {
Type : schema . TypeSet ,
Elem : & schema . Schema { Type : schema . TypeString } ,
Optional : true ,
ForceNew : true ,
Computed : true ,
Set : schema . HashString ,
} ,
"cluster_identifier" : & schema . Schema {
Type : schema . TypeString ,
Required : true ,
ForceNew : true ,
ValidateFunc : validateRdsId ,
} ,
"cluster_members" : & schema . Schema {
Type : schema . TypeSet ,
Elem : & schema . Schema { Type : schema . TypeString } ,
Optional : true ,
Computed : true ,
Set : schema . HashString ,
} ,
"database_name" : & schema . Schema {
Type : schema . TypeString ,
Optional : true ,
Computed : true ,
ForceNew : true ,
} ,
"db_subnet_group_name" : & schema . Schema {
Type : schema . TypeString ,
Optional : true ,
ForceNew : true ,
Computed : true ,
} ,
2016-06-09 23:45:29 +02:00
// TODO: remove parameter_group_name
// See https://github.com/hashicorp/terraform/issues/7046
// Likely need migration to remove from state
2016-02-23 11:52:07 +01:00
"parameter_group_name" : & schema . Schema {
2016-06-09 23:45:29 +02:00
Type : schema . TypeString ,
Optional : true ,
Computed : true ,
Deprecated : "Use db_cluster_parameter_group_name instead. This attribute will be removed in a future version" ,
} ,
"db_cluster_parameter_group_name" : & schema . Schema {
2016-02-23 11:52:07 +01:00
Type : schema . TypeString ,
Optional : true ,
Computed : true ,
} ,
2015-10-07 18:27:24 +02:00
"endpoint" : & schema . Schema {
Type : schema . TypeString ,
Computed : true ,
} ,
"engine" : & schema . Schema {
Type : schema . TypeString ,
Computed : true ,
} ,
2016-03-08 22:48:04 +01:00
"storage_encrypted" : & schema . Schema {
Type : schema . TypeBool ,
Optional : true ,
2016-03-08 23:15:01 +01:00
Default : false ,
2016-03-08 22:48:04 +01:00
ForceNew : true ,
} ,
2015-10-07 18:27:24 +02:00
"final_snapshot_identifier" : & schema . Schema {
Type : schema . TypeString ,
Optional : true ,
ValidateFunc : func ( v interface { } , k string ) ( ws [ ] string , es [ ] error ) {
value := v . ( string )
if ! regexp . MustCompile ( ` ^[0-9A-Za-z-]+$ ` ) . MatchString ( value ) {
es = append ( es , fmt . Errorf (
"only alphanumeric characters and hyphens allowed in %q" , k ) )
}
if regexp . MustCompile ( ` -- ` ) . MatchString ( value ) {
es = append ( es , fmt . Errorf ( "%q cannot contain two consecutive hyphens" , k ) )
}
if regexp . MustCompile ( ` -$ ` ) . MatchString ( value ) {
es = append ( es , fmt . Errorf ( "%q cannot end in a hyphen" , k ) )
}
return
} ,
} ,
2016-05-20 19:52:26 +02:00
"skip_final_snapshot" : & schema . Schema {
Type : schema . TypeBool ,
Optional : true ,
Default : true ,
} ,
2015-10-07 18:27:24 +02:00
"master_username" : & schema . Schema {
Type : schema . TypeString ,
2016-06-29 10:02:26 +02:00
Computed : true ,
Optional : true ,
2015-10-07 18:27:24 +02:00
ForceNew : true ,
} ,
"master_password" : & schema . Schema {
Type : schema . TypeString ,
2016-06-29 10:02:26 +02:00
Optional : true ,
} ,
"snapshot_identifier" : & schema . Schema {
Type : schema . TypeString ,
Computed : false ,
Optional : true ,
Elem : & schema . Schema { Type : schema . TypeString } ,
2015-10-07 18:27:24 +02:00
} ,
"port" : & schema . Schema {
Type : schema . TypeInt ,
Optional : true ,
Computed : true ,
} ,
// apply_immediately is used to determine when the update modifications
// take place.
// See http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.DBInstance.Modifying.html
"apply_immediately" : & schema . Schema {
Type : schema . TypeBool ,
Optional : true ,
Computed : true ,
} ,
"vpc_security_group_ids" : & schema . Schema {
Type : schema . TypeSet ,
Optional : true ,
Computed : true ,
Elem : & schema . Schema { Type : schema . TypeString } ,
Set : schema . HashString ,
} ,
2015-11-04 20:40:22 +01:00
"preferred_backup_window" : & schema . Schema {
Type : schema . TypeString ,
Optional : true ,
2015-11-05 11:25:01 +01:00
Computed : true ,
2015-11-04 20:40:22 +01:00
} ,
"preferred_maintenance_window" : & schema . Schema {
Type : schema . TypeString ,
Optional : true ,
2015-11-05 11:25:01 +01:00
Computed : true ,
2015-11-04 20:40:22 +01:00
StateFunc : func ( val interface { } ) string {
2015-11-05 11:25:01 +01:00
if val == nil {
return ""
}
2015-11-04 20:40:22 +01:00
return strings . ToLower ( val . ( string ) )
} ,
} ,
"backup_retention_period" : & schema . Schema {
Type : schema . TypeInt ,
Optional : true ,
Default : 1 ,
ValidateFunc : func ( v interface { } , k string ) ( ws [ ] string , es [ ] error ) {
value := v . ( int )
if value > 35 {
es = append ( es , fmt . Errorf (
"backup retention period cannot be more than 35 days" ) )
}
return
} ,
} ,
2015-10-07 18:27:24 +02:00
} ,
}
2015-08-04 23:24:55 +02:00
}
func resourceAwsRDSClusterCreate ( d * schema . ResourceData , meta interface { } ) error {
2015-10-07 18:27:24 +02:00
conn := meta . ( * AWSClient ) . rdsconn
2016-06-29 10:02:26 +02:00
if _ , ok := d . GetOk ( "snapshot_identifier" ) ; ok {
opts := rds . RestoreDBClusterFromSnapshotInput {
DBClusterIdentifier : aws . String ( d . Get ( "cluster_identifier" ) . ( string ) ) ,
SnapshotIdentifier : aws . String ( d . Get ( "snapshot_identifier" ) . ( string ) ) ,
Engine : aws . String ( "aurora" ) ,
}
if attr := d . Get ( "availability_zones" ) . ( * schema . Set ) ; attr . Len ( ) > 0 {
opts . AvailabilityZones = expandStringList ( attr . List ( ) )
}
if attr , ok := d . GetOk ( "db_subnet_group_name" ) ; ok {
opts . DBSubnetGroupName = aws . String ( attr . ( string ) )
}
if attr , ok := d . GetOk ( "database_name" ) ; ok {
opts . DatabaseName = aws . String ( attr . ( string ) )
}
if attr , ok := d . GetOk ( "option_group_name" ) ; ok {
opts . OptionGroupName = aws . String ( attr . ( string ) )
}
if attr , ok := d . GetOk ( "port" ) ; ok {
opts . Port = aws . Int64 ( int64 ( attr . ( int ) ) )
}
var sgUpdate bool
if attr := d . Get ( "vpc_security_group_ids" ) . ( * schema . Set ) ; attr . Len ( ) > 0 {
sgUpdate = true
opts . VpcSecurityGroupIds = expandStringList ( attr . List ( ) )
}
log . Printf ( "[DEBUG] RDS Cluster restore from snapshot configuration: %s" , opts )
_ , err := conn . RestoreDBClusterFromSnapshot ( & opts )
if err != nil {
return fmt . Errorf ( "Error creating RDS Cluster: %s" , err )
}
if sgUpdate {
log . Printf ( "[INFO] RDS Cluster is restoring from snapshot with default security, but custom security should be set, will now update after snapshot is restored!" )
d . SetId ( d . Get ( "cluster_identifier" ) . ( string ) )
log . Printf ( "[INFO] RDS Cluster Instance ID: %s" , d . Id ( ) )
log . Println ( "[INFO] Waiting for RDS Cluster to be available" )
stateConf := & resource . StateChangeConf {
Pending : [ ] string { "creating" , "backing-up" , "modifying" } ,
Target : [ ] string { "available" } ,
Refresh : resourceAwsRDSClusterStateRefreshFunc ( d , meta ) ,
Timeout : 5 * time . Minute ,
MinTimeout : 3 * time . Second ,
Delay : 30 * time . Second , // Wait 30 secs before starting
}
// Wait, catching any errors
_ , err := stateConf . WaitForState ( )
if err != nil {
return err
}
err = resourceAwsRDSClusterInstanceUpdate ( d , meta )
if err != nil {
return err
}
}
} else {
if _ , ok := d . GetOk ( "master_password" ) ; ! ok {
return fmt . Errorf ( ` provider.aws: aws_rds_cluster: %s: "master_password": required field is not set ` , d . Get ( "name" ) . ( string ) )
}
if _ , ok := d . GetOk ( "master_username" ) ; ! ok {
return fmt . Errorf ( ` provider.aws: aws_rds_cluster: %s: "master_username": required field is not set ` , d . Get ( "name" ) . ( string ) )
}
createOpts := & rds . CreateDBClusterInput {
DBClusterIdentifier : aws . String ( d . Get ( "cluster_identifier" ) . ( string ) ) ,
Engine : aws . String ( "aurora" ) ,
MasterUserPassword : aws . String ( d . Get ( "master_password" ) . ( string ) ) ,
MasterUsername : aws . String ( d . Get ( "master_username" ) . ( string ) ) ,
StorageEncrypted : aws . Bool ( d . Get ( "storage_encrypted" ) . ( bool ) ) ,
}
if v := d . Get ( "database_name" ) ; v . ( string ) != "" {
createOpts . DatabaseName = aws . String ( v . ( string ) )
}
if attr , ok := d . GetOk ( "port" ) ; ok {
createOpts . Port = aws . Int64 ( int64 ( attr . ( int ) ) )
}
if attr , ok := d . GetOk ( "db_subnet_group_name" ) ; ok {
createOpts . DBSubnetGroupName = aws . String ( attr . ( string ) )
}
if attr , ok := d . GetOk ( "parameter_group_name" ) ; ok {
createOpts . DBClusterParameterGroupName = aws . String ( attr . ( string ) )
}
if attr , ok := d . GetOk ( "db_cluster_parameter_group_name" ) ; ok {
createOpts . DBClusterParameterGroupName = aws . String ( attr . ( string ) )
}
if attr := d . Get ( "vpc_security_group_ids" ) . ( * schema . Set ) ; attr . Len ( ) > 0 {
createOpts . VpcSecurityGroupIds = expandStringList ( attr . List ( ) )
}
if attr := d . Get ( "availability_zones" ) . ( * schema . Set ) ; attr . Len ( ) > 0 {
createOpts . AvailabilityZones = expandStringList ( attr . List ( ) )
}
if v , ok := d . GetOk ( "backup_retention_period" ) ; ok {
createOpts . BackupRetentionPeriod = aws . Int64 ( int64 ( v . ( int ) ) )
}
if v , ok := d . GetOk ( "preferred_backup_window" ) ; ok {
createOpts . PreferredBackupWindow = aws . String ( v . ( string ) )
}
if v , ok := d . GetOk ( "preferred_maintenance_window" ) ; ok {
createOpts . PreferredMaintenanceWindow = aws . String ( v . ( string ) )
}
log . Printf ( "[DEBUG] RDS Cluster create options: %s" , createOpts )
resp , err := conn . CreateDBCluster ( createOpts )
if err != nil {
log . Printf ( "[ERROR] Error creating RDS Cluster: %s" , err )
return err
}
log . Printf ( "[DEBUG]: RDS Cluster create response: %s" , resp )
2015-10-07 18:27:24 +02:00
}
2016-06-29 10:02:26 +02:00
d . SetId ( d . Get ( "cluster_identifier" ) . ( string ) )
2015-10-07 18:27:24 +02:00
2016-06-29 10:02:26 +02:00
log . Printf ( "[INFO] RDS Cluster ID: %s" , d . Id ( ) )
2015-11-04 20:40:22 +01:00
2016-06-29 10:02:26 +02:00
log . Println (
"[INFO] Waiting for RDS Cluster to be available" )
2015-10-07 18:27:24 +02:00
stateConf := & resource . StateChangeConf {
Pending : [ ] string { "creating" , "backing-up" , "modifying" } ,
2016-01-21 02:20:41 +01:00
Target : [ ] string { "available" } ,
2015-10-07 18:27:24 +02:00
Refresh : resourceAwsRDSClusterStateRefreshFunc ( d , meta ) ,
Timeout : 5 * time . Minute ,
MinTimeout : 3 * time . Second ,
}
// Wait, catching any errors
2016-06-29 10:02:26 +02:00
_ , err := stateConf . WaitForState ( )
2015-10-07 18:27:24 +02:00
if err != nil {
return fmt . Errorf ( "[WARN] Error waiting for RDS Cluster state to be \"available\": %s" , err )
}
return resourceAwsRDSClusterRead ( d , meta )
2015-08-04 23:24:55 +02:00
}
func resourceAwsRDSClusterRead ( d * schema . ResourceData , meta interface { } ) error {
2015-10-07 18:27:24 +02:00
conn := meta . ( * AWSClient ) . rdsconn
resp , err := conn . DescribeDBClusters ( & rds . DescribeDBClustersInput {
DBClusterIdentifier : aws . String ( d . Id ( ) ) ,
} )
if err != nil {
if awsErr , ok := err . ( awserr . Error ) ; ok {
if "DBClusterNotFoundFault" == awsErr . Code ( ) {
d . SetId ( "" )
log . Printf ( "[DEBUG] RDS Cluster (%s) not found" , d . Id ( ) )
return nil
}
}
log . Printf ( "[DEBUG] Error describing RDS Cluster (%s)" , d . Id ( ) )
return err
}
var dbc * rds . DBCluster
for _ , c := range resp . DBClusters {
if * c . DBClusterIdentifier == d . Id ( ) {
dbc = c
}
}
if dbc == nil {
log . Printf ( "[WARN] RDS Cluster (%s) not found" , d . Id ( ) )
d . SetId ( "" )
return nil
}
if err := d . Set ( "availability_zones" , aws . StringValueSlice ( dbc . AvailabilityZones ) ) ; err != nil {
return fmt . Errorf ( "[DEBUG] Error saving AvailabilityZones to state for RDS Cluster (%s): %s" , d . Id ( ) , err )
}
2016-01-26 17:35:21 +01:00
// Only set the DatabaseName if it is not nil. There is a known API bug where
// RDS accepts a DatabaseName but does not return it, causing a perpetual
// diff.
// See https://github.com/hashicorp/terraform/issues/4671 for backstory
if dbc . DatabaseName != nil {
d . Set ( "database_name" , dbc . DatabaseName )
}
2015-10-07 18:27:24 +02:00
d . Set ( "db_subnet_group_name" , dbc . DBSubnetGroup )
2016-02-23 11:52:07 +01:00
d . Set ( "parameter_group_name" , dbc . DBClusterParameterGroup )
2016-06-09 23:45:29 +02:00
d . Set ( "db_cluster_parameter_group_name" , dbc . DBClusterParameterGroup )
2015-10-07 18:27:24 +02:00
d . Set ( "endpoint" , dbc . Endpoint )
d . Set ( "engine" , dbc . Engine )
d . Set ( "master_username" , dbc . MasterUsername )
d . Set ( "port" , dbc . Port )
2016-03-08 22:48:04 +01:00
d . Set ( "storage_encrypted" , dbc . StorageEncrypted )
2015-11-04 20:40:22 +01:00
d . Set ( "backup_retention_period" , dbc . BackupRetentionPeriod )
d . Set ( "preferred_backup_window" , dbc . PreferredBackupWindow )
d . Set ( "preferred_maintenance_window" , dbc . PreferredMaintenanceWindow )
2015-10-07 18:27:24 +02:00
var vpcg [ ] string
for _ , g := range dbc . VpcSecurityGroups {
vpcg = append ( vpcg , * g . VpcSecurityGroupId )
}
if err := d . Set ( "vpc_security_group_ids" , vpcg ) ; err != nil {
return fmt . Errorf ( "[DEBUG] Error saving VPC Security Group IDs to state for RDS Cluster (%s): %s" , d . Id ( ) , err )
}
var cm [ ] string
for _ , m := range dbc . DBClusterMembers {
cm = append ( cm , * m . DBInstanceIdentifier )
}
if err := d . Set ( "cluster_members" , cm ) ; err != nil {
return fmt . Errorf ( "[DEBUG] Error saving RDS Cluster Members to state for RDS Cluster (%s): %s" , d . Id ( ) , err )
}
return nil
2015-08-04 23:24:55 +02:00
}
func resourceAwsRDSClusterUpdate ( d * schema . ResourceData , meta interface { } ) error {
2015-10-07 18:27:24 +02:00
conn := meta . ( * AWSClient ) . rdsconn
req := & rds . ModifyDBClusterInput {
ApplyImmediately : aws . Bool ( d . Get ( "apply_immediately" ) . ( bool ) ) ,
DBClusterIdentifier : aws . String ( d . Id ( ) ) ,
}
if d . HasChange ( "master_password" ) {
req . MasterUserPassword = aws . String ( d . Get ( "master_password" ) . ( string ) )
}
if d . HasChange ( "vpc_security_group_ids" ) {
if attr := d . Get ( "vpc_security_group_ids" ) . ( * schema . Set ) ; attr . Len ( ) > 0 {
req . VpcSecurityGroupIds = expandStringList ( attr . List ( ) )
} else {
req . VpcSecurityGroupIds = [ ] * string { }
}
}
2015-11-04 20:40:22 +01:00
if d . HasChange ( "preferred_backup_window" ) {
req . PreferredBackupWindow = aws . String ( d . Get ( "preferred_backup_window" ) . ( string ) )
}
2015-11-04 22:06:41 +01:00
if d . HasChange ( "preferred_maintenance_window" ) {
req . PreferredMaintenanceWindow = aws . String ( d . Get ( "preferred_maintenance_window" ) . ( string ) )
2015-11-04 20:40:22 +01:00
}
2015-11-04 22:06:41 +01:00
if d . HasChange ( "backup_retention_period" ) {
req . BackupRetentionPeriod = aws . Int64 ( int64 ( d . Get ( "backup_retention_period" ) . ( int ) ) )
2015-11-04 20:40:22 +01:00
}
2016-02-23 11:52:07 +01:00
if d . HasChange ( "parameter_group_name" ) {
d . SetPartial ( "parameter_group_name" )
req . DBClusterParameterGroupName = aws . String ( d . Get ( "parameter_group_name" ) . ( string ) )
}
2016-06-09 23:45:29 +02:00
if d . HasChange ( "db_cluster_parameter_group_name" ) {
d . SetPartial ( "db_cluster_parameter_group_name" )
req . DBClusterParameterGroupName = aws . String ( d . Get ( "db_cluster_parameter_group_name" ) . ( string ) )
}
2015-10-07 18:27:24 +02:00
_ , err := conn . ModifyDBCluster ( req )
if err != nil {
return fmt . Errorf ( "[WARN] Error modifying RDS Cluster (%s): %s" , d . Id ( ) , err )
}
return resourceAwsRDSClusterRead ( d , meta )
2015-08-04 23:24:55 +02:00
}
func resourceAwsRDSClusterDelete ( d * schema . ResourceData , meta interface { } ) error {
2015-10-07 18:27:24 +02:00
conn := meta . ( * AWSClient ) . rdsconn
log . Printf ( "[DEBUG] Destroying RDS Cluster (%s)" , d . Id ( ) )
deleteOpts := rds . DeleteDBClusterInput {
DBClusterIdentifier : aws . String ( d . Id ( ) ) ,
}
2016-05-20 19:52:26 +02:00
skipFinalSnapshot := d . Get ( "skip_final_snapshot" ) . ( bool )
deleteOpts . SkipFinalSnapshot = aws . Bool ( skipFinalSnapshot )
if skipFinalSnapshot == false {
if name , present := d . GetOk ( "final_snapshot_identifier" ) ; present {
deleteOpts . FinalDBSnapshotIdentifier = aws . String ( name . ( string ) )
} else {
return fmt . Errorf ( "RDS Cluster FinalSnapshotIdentifier is required when a final snapshot is required" )
}
2015-10-07 18:27:24 +02:00
}
log . Printf ( "[DEBUG] RDS Cluster delete options: %s" , deleteOpts )
_ , err := conn . DeleteDBCluster ( & deleteOpts )
stateConf := & resource . StateChangeConf {
2016-04-19 20:15:45 +02:00
Pending : [ ] string { "available" , "deleting" , "backing-up" , "modifying" } ,
2016-01-21 02:20:41 +01:00
Target : [ ] string { "destroyed" } ,
2015-10-07 18:27:24 +02:00
Refresh : resourceAwsRDSClusterStateRefreshFunc ( d , meta ) ,
Timeout : 5 * time . Minute ,
MinTimeout : 3 * time . Second ,
}
// Wait, catching any errors
_ , err = stateConf . WaitForState ( )
if err != nil {
return fmt . Errorf ( "[WARN] Error deleting RDS Cluster (%s): %s" , d . Id ( ) , err )
}
return nil
2015-08-04 23:24:55 +02:00
}
func resourceAwsRDSClusterStateRefreshFunc (
2015-10-07 18:27:24 +02:00
d * schema . ResourceData , meta interface { } ) resource . StateRefreshFunc {
return func ( ) ( interface { } , string , error ) {
conn := meta . ( * AWSClient ) . rdsconn
resp , err := conn . DescribeDBClusters ( & rds . DescribeDBClustersInput {
DBClusterIdentifier : aws . String ( d . Id ( ) ) ,
} )
if err != nil {
if awsErr , ok := err . ( awserr . Error ) ; ok {
if "DBClusterNotFoundFault" == awsErr . Code ( ) {
return 42 , "destroyed" , nil
}
}
log . Printf ( "[WARN] Error on retrieving DB Cluster (%s) when waiting: %s" , d . Id ( ) , err )
return nil , "" , err
}
var dbc * rds . DBCluster
for _ , c := range resp . DBClusters {
if * c . DBClusterIdentifier == d . Id ( ) {
dbc = c
}
}
if dbc == nil {
return 42 , "destroyed" , nil
}
if dbc . Status != nil {
log . Printf ( "[DEBUG] DB Cluster status (%s): %s" , d . Id ( ) , * dbc . Status )
}
return dbc , * dbc . Status , nil
}
2015-08-04 23:24:55 +02:00
}