github.com/nicgrayson/terraform@v0.4.3-0.20150415203910-c4de50829380/builtin/providers/aws/resource_aws_db_instance.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"time"
     7  
     8  	"github.com/awslabs/aws-sdk-go/aws"
     9  	"github.com/awslabs/aws-sdk-go/service/iam"
    10  	"github.com/awslabs/aws-sdk-go/service/rds"
    11  
    12  	"github.com/hashicorp/terraform/helper/hashcode"
    13  	"github.com/hashicorp/terraform/helper/resource"
    14  	"github.com/hashicorp/terraform/helper/schema"
    15  )
    16  
    17  func resourceAwsDbInstance() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceAwsDbInstanceCreate,
    20  		Read:   resourceAwsDbInstanceRead,
    21  		Update: resourceAwsDbInstanceUpdate,
    22  		Delete: resourceAwsDbInstanceDelete,
    23  
    24  		Schema: map[string]*schema.Schema{
    25  			"name": &schema.Schema{
    26  				Type:     schema.TypeString,
    27  				Optional: true,
    28  				ForceNew: true,
    29  			},
    30  
    31  			"username": &schema.Schema{
    32  				Type:     schema.TypeString,
    33  				Required: true,
    34  				ForceNew: true,
    35  			},
    36  
    37  			"password": &schema.Schema{
    38  				Type:     schema.TypeString,
    39  				Required: true,
    40  			},
    41  
    42  			"engine": &schema.Schema{
    43  				Type:     schema.TypeString,
    44  				Required: true,
    45  				ForceNew: true,
    46  			},
    47  
    48  			"engine_version": &schema.Schema{
    49  				Type:     schema.TypeString,
    50  				Required: true,
    51  			},
    52  
    53  			"storage_encrypted": &schema.Schema{
    54  				Type:     schema.TypeBool,
    55  				Optional: true,
    56  				ForceNew: true,
    57  			},
    58  
    59  			"allocated_storage": &schema.Schema{
    60  				Type:     schema.TypeInt,
    61  				Required: true,
    62  			},
    63  
    64  			"storage_type": &schema.Schema{
    65  				Type:     schema.TypeString,
    66  				Optional: true,
    67  				Computed: true,
    68  			},
    69  
    70  			"identifier": &schema.Schema{
    71  				Type:     schema.TypeString,
    72  				Required: true,
    73  				ForceNew: true,
    74  			},
    75  
    76  			"instance_class": &schema.Schema{
    77  				Type:     schema.TypeString,
    78  				Required: true,
    79  			},
    80  
    81  			"availability_zone": &schema.Schema{
    82  				Type:     schema.TypeString,
    83  				Optional: true,
    84  				Computed: true,
    85  				ForceNew: true,
    86  			},
    87  
    88  			"backup_retention_period": &schema.Schema{
    89  				Type:     schema.TypeInt,
    90  				Optional: true,
    91  				Default:  1,
    92  			},
    93  
    94  			"backup_window": &schema.Schema{
    95  				Type:     schema.TypeString,
    96  				Optional: true,
    97  				Computed: true,
    98  			},
    99  
   100  			"iops": &schema.Schema{
   101  				Type:     schema.TypeInt,
   102  				Optional: true,
   103  			},
   104  
   105  			"maintenance_window": &schema.Schema{
   106  				Type:     schema.TypeString,
   107  				Optional: true,
   108  				Computed: true,
   109  			},
   110  
   111  			"multi_az": &schema.Schema{
   112  				Type:     schema.TypeBool,
   113  				Optional: true,
   114  				Computed: true,
   115  			},
   116  
   117  			"port": &schema.Schema{
   118  				Type:     schema.TypeInt,
   119  				Optional: true,
   120  				Computed: true,
   121  				ForceNew: true,
   122  			},
   123  
   124  			"publicly_accessible": &schema.Schema{
   125  				Type:     schema.TypeBool,
   126  				Optional: true,
   127  				ForceNew: true,
   128  			},
   129  
   130  			"vpc_security_group_ids": &schema.Schema{
   131  				Type:     schema.TypeSet,
   132  				Optional: true,
   133  				Computed: true,
   134  				Elem:     &schema.Schema{Type: schema.TypeString},
   135  				Set: func(v interface{}) int {
   136  					return hashcode.String(v.(string))
   137  				},
   138  			},
   139  
   140  			"security_group_names": &schema.Schema{
   141  				Type:     schema.TypeSet,
   142  				Optional: true,
   143  				Elem:     &schema.Schema{Type: schema.TypeString},
   144  				Set: func(v interface{}) int {
   145  					return hashcode.String(v.(string))
   146  				},
   147  			},
   148  
   149  			"final_snapshot_identifier": &schema.Schema{
   150  				Type:     schema.TypeString,
   151  				Optional: true,
   152  			},
   153  
   154  			"db_subnet_group_name": &schema.Schema{
   155  				Type:     schema.TypeString,
   156  				Optional: true,
   157  				ForceNew: true,
   158  				Computed: true,
   159  			},
   160  
   161  			"parameter_group_name": &schema.Schema{
   162  				Type:     schema.TypeString,
   163  				Optional: true,
   164  				Computed: true,
   165  			},
   166  
   167  			"address": &schema.Schema{
   168  				Type:     schema.TypeString,
   169  				Computed: true,
   170  			},
   171  
   172  			"endpoint": &schema.Schema{
   173  				Type:     schema.TypeString,
   174  				Computed: true,
   175  			},
   176  
   177  			"status": &schema.Schema{
   178  				Type:     schema.TypeString,
   179  				Computed: true,
   180  			},
   181  
   182  			// apply_immediately is used to determine when the update modifications
   183  			// take place.
   184  			// See http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.DBInstance.Modifying.html
   185  			"apply_immediately": &schema.Schema{
   186  				Type:     schema.TypeBool,
   187  				Optional: true,
   188  				Computed: true,
   189  			},
   190  
   191  			"tags": tagsSchema(),
   192  		},
   193  	}
   194  }
   195  
   196  func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error {
   197  	conn := meta.(*AWSClient).rdsconn
   198  	tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{}))
   199  	opts := rds.CreateDBInstanceInput{
   200  		AllocatedStorage:     aws.Long(int64(d.Get("allocated_storage").(int))),
   201  		DBInstanceClass:      aws.String(d.Get("instance_class").(string)),
   202  		DBInstanceIdentifier: aws.String(d.Get("identifier").(string)),
   203  		DBName:               aws.String(d.Get("name").(string)),
   204  		MasterUsername:       aws.String(d.Get("username").(string)),
   205  		MasterUserPassword:   aws.String(d.Get("password").(string)),
   206  		Engine:               aws.String(d.Get("engine").(string)),
   207  		EngineVersion:        aws.String(d.Get("engine_version").(string)),
   208  		StorageEncrypted:     aws.Boolean(d.Get("storage_encrypted").(bool)),
   209  		Tags:                 tags,
   210  	}
   211  
   212  	if attr, ok := d.GetOk("storage_type"); ok {
   213  		opts.StorageType = aws.String(attr.(string))
   214  	}
   215  
   216  	attr := d.Get("backup_retention_period")
   217  	opts.BackupRetentionPeriod = aws.Long(int64(attr.(int)))
   218  
   219  	if attr, ok := d.GetOk("iops"); ok {
   220  		opts.IOPS = aws.Long(int64(attr.(int)))
   221  	}
   222  
   223  	if attr, ok := d.GetOk("port"); ok {
   224  		opts.Port = aws.Long(int64(attr.(int)))
   225  	}
   226  
   227  	if attr, ok := d.GetOk("multi_az"); ok {
   228  		opts.MultiAZ = aws.Boolean(attr.(bool))
   229  	}
   230  
   231  	if attr, ok := d.GetOk("availability_zone"); ok {
   232  		opts.AvailabilityZone = aws.String(attr.(string))
   233  	}
   234  
   235  	if attr, ok := d.GetOk("maintenance_window"); ok {
   236  		opts.PreferredMaintenanceWindow = aws.String(attr.(string))
   237  	}
   238  
   239  	if attr, ok := d.GetOk("backup_window"); ok {
   240  		opts.PreferredBackupWindow = aws.String(attr.(string))
   241  	}
   242  
   243  	if attr, ok := d.GetOk("publicly_accessible"); ok {
   244  		opts.PubliclyAccessible = aws.Boolean(attr.(bool))
   245  	}
   246  
   247  	if attr, ok := d.GetOk("db_subnet_group_name"); ok {
   248  		opts.DBSubnetGroupName = aws.String(attr.(string))
   249  	}
   250  
   251  	if attr, ok := d.GetOk("parameter_group_name"); ok {
   252  		opts.DBParameterGroupName = aws.String(attr.(string))
   253  	}
   254  
   255  	if attr := d.Get("vpc_security_group_ids").(*schema.Set); attr.Len() > 0 {
   256  		var s []*string
   257  		for _, v := range attr.List() {
   258  			s = append(s, aws.String(v.(string)))
   259  		}
   260  		opts.VPCSecurityGroupIDs = s
   261  	}
   262  
   263  	if attr := d.Get("security_group_names").(*schema.Set); attr.Len() > 0 {
   264  		var s []*string
   265  		for _, v := range attr.List() {
   266  			s = append(s, aws.String(v.(string)))
   267  		}
   268  		opts.DBSecurityGroups = s
   269  	}
   270  
   271  	log.Printf("[DEBUG] DB Instance create configuration: %#v", opts)
   272  	_, err := conn.CreateDBInstance(&opts)
   273  	if err != nil {
   274  		return fmt.Errorf("Error creating DB Instance: %s", err)
   275  	}
   276  
   277  	d.SetId(d.Get("identifier").(string))
   278  
   279  	log.Printf("[INFO] DB Instance ID: %s", d.Id())
   280  
   281  	log.Println(
   282  		"[INFO] Waiting for DB Instance to be available")
   283  
   284  	stateConf := &resource.StateChangeConf{
   285  		Pending:    []string{"creating", "backing-up", "modifying"},
   286  		Target:     "available",
   287  		Refresh:    resourceAwsDbInstanceStateRefreshFunc(d, meta),
   288  		Timeout:    40 * time.Minute,
   289  		MinTimeout: 10 * time.Second,
   290  		Delay:      30 * time.Second, // Wait 30 secs before starting
   291  	}
   292  
   293  	// Wait, catching any errors
   294  	_, err = stateConf.WaitForState()
   295  	if err != nil {
   296  		return err
   297  	}
   298  
   299  	return resourceAwsDbInstanceRead(d, meta)
   300  }
   301  
   302  func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error {
   303  	v, err := resourceAwsBbInstanceRetrieve(d, meta)
   304  
   305  	if err != nil {
   306  		return err
   307  	}
   308  	if v == nil {
   309  		d.SetId("")
   310  		return nil
   311  	}
   312  
   313  	d.Set("name", v.DBName)
   314  	d.Set("username", v.MasterUsername)
   315  	d.Set("engine", v.Engine)
   316  	d.Set("engine_version", v.EngineVersion)
   317  	d.Set("allocated_storage", v.AllocatedStorage)
   318  	d.Set("storage_type", v.StorageType)
   319  	d.Set("instance_class", v.DBInstanceClass)
   320  	d.Set("availability_zone", v.AvailabilityZone)
   321  	d.Set("backup_retention_period", v.BackupRetentionPeriod)
   322  	d.Set("backup_window", v.PreferredBackupWindow)
   323  	d.Set("maintenance_window", v.PreferredMaintenanceWindow)
   324  	d.Set("multi_az", v.MultiAZ)
   325  	if v.DBSubnetGroup != nil {
   326  		d.Set("db_subnet_group_name", v.DBSubnetGroup.DBSubnetGroupName)
   327  	}
   328  
   329  	if len(v.DBParameterGroups) > 0 {
   330  		d.Set("parameter_group_name", v.DBParameterGroups[0].DBParameterGroupName)
   331  	}
   332  
   333  	if v.Endpoint != nil {
   334  		d.Set("port", v.Endpoint.Port)
   335  		d.Set("address", v.Endpoint.Address)
   336  
   337  		if v.Endpoint.Address != nil && v.Endpoint.Port != nil {
   338  			d.Set("endpoint",
   339  				fmt.Sprintf("%s:%d", *v.Endpoint.Address, *v.Endpoint.Port))
   340  		}
   341  	}
   342  
   343  	d.Set("status", v.DBInstanceStatus)
   344  	d.Set("storage_encrypted", v.StorageEncrypted)
   345  
   346  	// list tags for resource
   347  	// set tags
   348  	conn := meta.(*AWSClient).rdsconn
   349  	arn, err := buildRDSARN(d, meta)
   350  	if err != nil {
   351  		name := "<empty>"
   352  		if v.DBName != nil && *v.DBName != "" {
   353  			name = *v.DBName
   354  		}
   355  
   356  		log.Printf("[DEBUG] Error building ARN for DB Instance, not setting Tags for DB %s", name)
   357  	} else {
   358  		resp, err := conn.ListTagsForResource(&rds.ListTagsForResourceInput{
   359  			ResourceName: aws.String(arn),
   360  		})
   361  
   362  		if err != nil {
   363  			log.Printf("[DEBUG] Error retreiving tags for ARN: %s", arn)
   364  		}
   365  
   366  		var dt []*rds.Tag
   367  		if len(resp.TagList) > 0 {
   368  			dt = resp.TagList
   369  		}
   370  		d.Set("tags", tagsToMapRDS(dt))
   371  	}
   372  
   373  	// Create an empty schema.Set to hold all vpc security group ids
   374  	ids := &schema.Set{
   375  		F: func(v interface{}) int {
   376  			return hashcode.String(v.(string))
   377  		},
   378  	}
   379  	for _, v := range v.VPCSecurityGroups {
   380  		ids.Add(*v.VPCSecurityGroupID)
   381  	}
   382  	d.Set("vpc_security_group_ids", ids)
   383  
   384  	// Create an empty schema.Set to hold all security group names
   385  	sgn := &schema.Set{
   386  		F: func(v interface{}) int {
   387  			return hashcode.String(v.(string))
   388  		},
   389  	}
   390  	for _, v := range v.DBSecurityGroups {
   391  		sgn.Add(*v.DBSecurityGroupName)
   392  	}
   393  	d.Set("security_group_names", sgn)
   394  
   395  	return nil
   396  }
   397  
   398  func resourceAwsDbInstanceDelete(d *schema.ResourceData, meta interface{}) error {
   399  	conn := meta.(*AWSClient).rdsconn
   400  
   401  	log.Printf("[DEBUG] DB Instance destroy: %v", d.Id())
   402  
   403  	opts := rds.DeleteDBInstanceInput{DBInstanceIdentifier: aws.String(d.Id())}
   404  
   405  	finalSnapshot := d.Get("final_snapshot_identifier").(string)
   406  	if finalSnapshot == "" {
   407  		opts.SkipFinalSnapshot = aws.Boolean(true)
   408  	} else {
   409  		opts.FinalDBSnapshotIdentifier = aws.String(finalSnapshot)
   410  	}
   411  
   412  	log.Printf("[DEBUG] DB Instance destroy configuration: %v", opts)
   413  	if _, err := conn.DeleteDBInstance(&opts); err != nil {
   414  		return err
   415  	}
   416  
   417  	log.Println(
   418  		"[INFO] Waiting for DB Instance to be destroyed")
   419  	stateConf := &resource.StateChangeConf{
   420  		Pending: []string{"creating", "backing-up",
   421  			"modifying", "deleting", "available"},
   422  		Target:     "",
   423  		Refresh:    resourceAwsDbInstanceStateRefreshFunc(d, meta),
   424  		Timeout:    40 * time.Minute,
   425  		MinTimeout: 10 * time.Second,
   426  		Delay:      30 * time.Second, // Wait 30 secs before starting
   427  	}
   428  	if _, err := stateConf.WaitForState(); err != nil {
   429  		return err
   430  	}
   431  
   432  	return nil
   433  }
   434  
   435  func resourceAwsDbInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
   436  	conn := meta.(*AWSClient).rdsconn
   437  
   438  	d.Partial(true)
   439  
   440  	req := &rds.ModifyDBInstanceInput{
   441  		ApplyImmediately:     aws.Boolean(d.Get("apply_immediately").(bool)),
   442  		DBInstanceIdentifier: aws.String(d.Id()),
   443  	}
   444  	d.SetPartial("apply_immediately")
   445  
   446  	if d.HasChange("allocated_storage") {
   447  		d.SetPartial("allocated_storage")
   448  		req.AllocatedStorage = aws.Long(int64(d.Get("allocated_storage").(int)))
   449  	}
   450  	if d.HasChange("backup_retention_period") {
   451  		d.SetPartial("backup_retention_period")
   452  		req.BackupRetentionPeriod = aws.Long(int64(d.Get("backup_retention_period").(int)))
   453  	}
   454  	if d.HasChange("instance_class") {
   455  		d.SetPartial("instance_class")
   456  		req.DBInstanceClass = aws.String(d.Get("instance_class").(string))
   457  	}
   458  	if d.HasChange("parameter_group_name") {
   459  		d.SetPartial("parameter_group_name")
   460  		req.DBParameterGroupName = aws.String(d.Get("parameter_group_name").(string))
   461  	}
   462  	if d.HasChange("engine_version") {
   463  		d.SetPartial("engine_version")
   464  		req.EngineVersion = aws.String(d.Get("engine_version").(string))
   465  	}
   466  	if d.HasChange("iops") {
   467  		d.SetPartial("iops")
   468  		req.IOPS = aws.Long(int64(d.Get("iops").(int)))
   469  	}
   470  	if d.HasChange("backup_window") {
   471  		d.SetPartial("backup_window")
   472  		req.PreferredBackupWindow = aws.String(d.Get("backup_window").(string))
   473  	}
   474  	if d.HasChange("maintenance_window") {
   475  		d.SetPartial("maintenance_window")
   476  		req.PreferredMaintenanceWindow = aws.String(d.Get("maintenance_window").(string))
   477  	}
   478  	if d.HasChange("password") {
   479  		d.SetPartial("password")
   480  		req.MasterUserPassword = aws.String(d.Get("password").(string))
   481  	}
   482  	if d.HasChange("multi_az") {
   483  		d.SetPartial("multi_az")
   484  		req.MultiAZ = aws.Boolean(d.Get("multi_az").(bool))
   485  	}
   486  	if d.HasChange("storage_type") {
   487  		d.SetPartial("storage_type")
   488  		req.StorageType = aws.String(d.Get("storage_type").(string))
   489  	}
   490  
   491  	if d.HasChange("vpc_security_group_ids") {
   492  		if attr := d.Get("vpc_security_group_ids").(*schema.Set); attr.Len() > 0 {
   493  			var s []*string
   494  			for _, v := range attr.List() {
   495  				s = append(s, aws.String(v.(string)))
   496  			}
   497  			req.VPCSecurityGroupIDs = s
   498  		}
   499  	}
   500  
   501  	if d.HasChange("vpc_security_group_ids") {
   502  		if attr := d.Get("security_group_names").(*schema.Set); attr.Len() > 0 {
   503  			var s []*string
   504  			for _, v := range attr.List() {
   505  				s = append(s, aws.String(v.(string)))
   506  			}
   507  			req.DBSecurityGroups = s
   508  		}
   509  	}
   510  
   511  	log.Printf("[DEBUG] DB Instance Modification request: %#v", req)
   512  	_, err := conn.ModifyDBInstance(req)
   513  	if err != nil {
   514  		return fmt.Errorf("Error modifying DB Instance %s: %s", d.Id(), err)
   515  	}
   516  
   517  	if arn, err := buildRDSARN(d, meta); err == nil {
   518  		if err := setTagsRDS(conn, d, arn); err != nil {
   519  			return err
   520  		} else {
   521  			d.SetPartial("tags")
   522  		}
   523  	}
   524  	d.Partial(false)
   525  	return resourceAwsDbInstanceRead(d, meta)
   526  }
   527  
   528  func resourceAwsBbInstanceRetrieve(
   529  	d *schema.ResourceData, meta interface{}) (*rds.DBInstance, error) {
   530  	conn := meta.(*AWSClient).rdsconn
   531  
   532  	opts := rds.DescribeDBInstancesInput{
   533  		DBInstanceIdentifier: aws.String(d.Id()),
   534  	}
   535  
   536  	log.Printf("[DEBUG] DB Instance describe configuration: %#v", opts)
   537  
   538  	resp, err := conn.DescribeDBInstances(&opts)
   539  
   540  	if err != nil {
   541  		dbinstanceerr, ok := err.(aws.APIError)
   542  		if ok && dbinstanceerr.Code == "DBInstanceNotFound" {
   543  			return nil, nil
   544  		}
   545  		return nil, fmt.Errorf("Error retrieving DB Instances: %s", err)
   546  	}
   547  
   548  	if len(resp.DBInstances) != 1 ||
   549  		*resp.DBInstances[0].DBInstanceIdentifier != d.Id() {
   550  		if err != nil {
   551  			return nil, nil
   552  		}
   553  	}
   554  
   555  	return resp.DBInstances[0], nil
   556  }
   557  
   558  func resourceAwsDbInstanceStateRefreshFunc(
   559  	d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
   560  	return func() (interface{}, string, error) {
   561  		v, err := resourceAwsBbInstanceRetrieve(d, meta)
   562  
   563  		if err != nil {
   564  			log.Printf("Error on retrieving DB Instance when waiting: %s", err)
   565  			return nil, "", err
   566  		}
   567  
   568  		if v == nil {
   569  			return nil, "", nil
   570  		}
   571  
   572  		return v, *v.DBInstanceStatus, nil
   573  	}
   574  }
   575  
   576  func buildRDSARN(d *schema.ResourceData, meta interface{}) (string, error) {
   577  	iamconn := meta.(*AWSClient).iamconn
   578  	region := meta.(*AWSClient).region
   579  	// An zero value GetUserInput{} defers to the currently logged in user
   580  	resp, err := iamconn.GetUser(&iam.GetUserInput{})
   581  	if err != nil {
   582  		return "", err
   583  	}
   584  	user := resp.User
   585  	arn := fmt.Sprintf("arn:aws:rds:%s:%s:db:%s", region, *user.UserID, d.Id())
   586  	return arn, nil
   587  }