From 5a90bc0bd10f425e81fa288744f7b58c1fbe4f95 Mon Sep 17 00:00:00 2001 From: Kentaro Terada Date: Sat, 7 Apr 2018 10:37:52 +0900 Subject: [PATCH 1/2] Add enabled_cloudwatch_logs_exports attributes for aws_db_instance --- aws/resource_aws_db_instance.go | 85 +++++++ aws/resource_aws_db_instance_test.go | 317 +++++++++++++++++++++++++++ 2 files changed, 402 insertions(+) diff --git a/aws/resource_aws_db_instance.go b/aws/resource_aws_db_instance.go index b2434659373..4ed3389378f 100644 --- a/aws/resource_aws_db_instance.go +++ b/aws/resource_aws_db_instance.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" ) func resourceAwsDbInstance() *schema.Resource { @@ -351,6 +352,21 @@ func resourceAwsDbInstance() *schema.Resource { Computed: true, }, + "enabled_cloudwatch_logs_exports": { + Type: schema.TypeList, + Computed: false, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + "audit", + "error", + "general", + "slowquery", + }, false), + }, + }, + "tags": tagsSchema(), }, } @@ -408,6 +424,10 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error opts.DBSubnetGroupName = aws.String(attr.(string)) } + if attr, ok := d.GetOk("enabled_cloudwatch_logs_exports"); ok && len(attr.([]interface{})) > 0 { + opts.EnableCloudwatchLogsExports = expandStringList(attr.([]interface{})) + } + if attr, ok := d.GetOk("kms_key_id"); ok { opts.KmsKeyId = aws.String(attr.(string)) if arnParts := strings.Split(v.(string), ":"); len(arnParts) >= 4 { @@ -462,6 +482,10 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error opts.DBSubnetGroupName = aws.String(attr.(string)) } + if attr, ok := d.GetOk("enabled_cloudwatch_logs_exports"); ok && len(attr.([]interface{})) > 0 { + opts.EnableCloudwatchLogsExports = expandStringList(attr.([]interface{})) + } + if attr, ok := d.GetOk("engine"); ok { opts.Engine = aws.String(attr.(string)) } @@ -628,6 +652,10 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error opts.DBSubnetGroupName = aws.String(attr.(string)) } + if attr, ok := d.GetOk("enabled_cloudwatch_logs_exports"); ok && len(attr.([]interface{})) > 0 { + opts.EnableCloudwatchLogsExports = expandStringList(attr.([]interface{})) + } + if attr, ok := d.GetOk("iops"); ok { opts.Iops = aws.Int64(int64(attr.(int))) } @@ -775,6 +803,10 @@ func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error { d.Set("monitoring_role_arn", v.MonitoringRoleArn) } + if v.EnabledCloudwatchLogsExports != nil { + d.Set("enabled_cloudwatch_logs_exports", v.EnabledCloudwatchLogsExports) + } + // list tags for resource // set tags conn := meta.(*AWSClient).rdsconn @@ -1020,6 +1052,12 @@ func resourceAwsDbInstanceUpdate(d *schema.ResourceData, meta interface{}) error requestUpdate = true } + if d.HasChange("enabled_cloudwatch_logs_exports") && !d.IsNewResource() { + d.SetPartial("enabled_cloudwatch_logs_exports") + req.CloudwatchLogsExportConfiguration = buildCloudwatchLogsExportConfiguration(d) + requestUpdate = true + } + if d.HasChange("iam_database_authentication_enabled") { req.EnableIAMDatabaseAuthentication = aws.Bool(d.Get("iam_database_authentication_enabled").(bool)) requestUpdate = true @@ -1151,10 +1189,55 @@ func resourceAwsDbInstanceStateRefreshFunc(id string, conn *rds.RDS) resource.St } } +func buildRDSARN(identifier, partition, accountid, region string) (string, error) { + if partition == "" { + return "", fmt.Errorf("Unable to construct RDS ARN because of missing AWS partition") + } + if accountid == "" { + return "", fmt.Errorf("Unable to construct RDS ARN because of missing AWS Account ID") + } + arn := fmt.Sprintf("arn:%s:rds:%s:%s:db:%s", partition, region, accountid, identifier) + return arn, nil +} + +func buildCloudwatchLogsExportConfiguration(d *schema.ResourceData) *rds.CloudwatchLogsExportConfiguration { + + oraw, nraw := d.GetChange("enabled_cloudwatch_logs_exports") + o := oraw.([]interface{}) + n := nraw.([]interface{}) + + create, disable := diffCloudwatchLogsExportConfiguration(o, n) + + return &rds.CloudwatchLogsExportConfiguration{ + EnableLogTypes: expandStringList(create), + DisableLogTypes: expandStringList(disable), + } +} + +func diffCloudwatchLogsExportConfiguration(old, new []interface{}) ([]interface{}, []interface{}) { + create := make([]interface{}, 0) + disable := make([]interface{}, 0) + + for _, n := range new { + if _, contains := sliceContainsString(old, n.(string)); !contains { + create = append(create, n) + } + } + + for _, o := range old { + if _, contains := sliceContainsString(new, o.(string)); !contains { + disable = append(disable, o) + } + } + + return create, disable +} + // Database instance status: http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.DBInstance.Status.html var resourceAwsDbInstanceCreatePendingStates = []string{ "backing-up", "configuring-enhanced-monitoring", + "configuring-log-exports", "creating", "maintenance", "modifying", @@ -1170,6 +1253,7 @@ var resourceAwsDbInstanceDeletePendingStates = []string{ "available", "backing-up", "configuring-enhanced-monitoring", + "configuring-log-exports", "creating", "deleting", "incompatible-parameters", @@ -1183,6 +1267,7 @@ var resourceAwsDbInstanceDeletePendingStates = []string{ var resourceAwsDbInstanceUpdatePendingStates = []string{ "backing-up", "configuring-enhanced-monitoring", + "configuring-log-exports", "creating", "maintenance", "modifying", diff --git a/aws/resource_aws_db_instance_test.go b/aws/resource_aws_db_instance_test.go index ab07c980f34..42490f89c8f 100644 --- a/aws/resource_aws_db_instance_test.go +++ b/aws/resource_aws_db_instance_test.go @@ -107,6 +107,10 @@ func TestAccAWSDBInstance_basic(t *testing.T) { "aws_db_instance.bar", "username", "foo"), resource.TestCheckResourceAttr( "aws_db_instance.bar", "parameter_group_name", "default.mysql5.6"), + resource.TestCheckResourceAttr( + "aws_db_instance.bar", "enabled_cloudwatch_logs_exports.0", "audit"), + resource.TestCheckResourceAttr( + "aws_db_instance.bar", "enabled_cloudwatch_logs_exports.1", "error"), resource.TestCheckResourceAttrSet("aws_db_instance.bar", "hosted_zone_id"), resource.TestCheckResourceAttrSet("aws_db_instance.bar", "ca_cert_identifier"), resource.TestCheckResourceAttrSet( @@ -496,6 +500,82 @@ func TestAccAWSDBInstance_ec2Classic(t *testing.T) { }) } +func TestAccAWSDBInstance_cloudwatchLogsExportConfiguration(t *testing.T) { + var v rds.DBInstance + + rInt := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDBInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDBInstanceConfigCloudwatchLogsExportConfiguration(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBInstanceExists("aws_db_instance.bar", &v), + ), + }, + }, + }) +} + +func TestAccAWSDBInstance_cloudwatchLogsExportConfigurationUpdate(t *testing.T) { + var v rds.DBInstance + + rInt := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDBInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSDBInstanceConfigCloudwatchLogsExportConfiguration(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBInstanceExists("aws_db_instance.bar", &v), + resource.TestCheckResourceAttr( + "aws_db_instance.bar", "enabled_cloudwatch_logs_exports.0", "audit"), + resource.TestCheckResourceAttr( + "aws_db_instance.bar", "enabled_cloudwatch_logs_exports.1", "error"), + ), + }, + { + Config: testAccAWSDBInstanceConfigCloudwatchLogsExportConfigurationAdd(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBInstanceExists("aws_db_instance.bar", &v), + resource.TestCheckResourceAttr( + "aws_db_instance.bar", "enabled_cloudwatch_logs_exports.0", "audit"), + resource.TestCheckResourceAttr( + "aws_db_instance.bar", "enabled_cloudwatch_logs_exports.1", "error"), + resource.TestCheckResourceAttr( + "aws_db_instance.bar", "enabled_cloudwatch_logs_exports.2", "general"), + ), + }, + { + Config: testAccAWSDBInstanceConfigCloudwatchLogsExportConfigurationModify(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBInstanceExists("aws_db_instance.bar", &v), + resource.TestCheckResourceAttr( + "aws_db_instance.bar", "enabled_cloudwatch_logs_exports.0", "audit"), + resource.TestCheckResourceAttr( + "aws_db_instance.bar", "enabled_cloudwatch_logs_exports.1", "general"), + resource.TestCheckResourceAttr( + "aws_db_instance.bar", "enabled_cloudwatch_logs_exports.2", "slowquery"), + ), + }, + { + Config: testAccAWSDBInstanceConfigCloudwatchLogsExportConfigurationDelete(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBInstanceExists("aws_db_instance.bar", &v), + resource.TestCheckNoResourceAttr( + "aws_db_instance.bar", "enabled_cloudwatch_logs_exports.0"), + ), + }, + }, + }) +} + func testAccCheckAWSDBInstanceDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).rdsconn @@ -777,6 +857,11 @@ resource "aws_db_instance" "bar" { timeouts { create = "30m" } + + enabled_cloudwatch_logs_exports = [ + "audit", + "error", + ] }` const testAccAWSDBInstanceConfig_namePrefix = ` @@ -1436,6 +1521,238 @@ resource "aws_db_instance" "bar" { } `, acctest.RandInt()) +func testAccAWSDBInstanceConfigCloudwatchLogsExportConfiguration(rInt int) string { + return fmt.Sprintf(` + + resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" + enable_dns_hostnames = true + tags { + Name = "terraform-testacc-db-instance-mssql-timezone" + } + } + + resource "aws_db_subnet_group" "rds_one" { + name = "tf_acc_test_%d" + description = "db subnets for rds_one" + + subnet_ids = ["${aws_subnet.main.id}", "${aws_subnet.other.id}"] + } + + resource "aws_subnet" "main" { + vpc_id = "${aws_vpc.foo.id}" + availability_zone = "us-west-2a" + cidr_block = "10.1.1.0/24" + tags { + Name = "tf-acc-db-instance-mssql-timezone-main" + } + } + + resource "aws_subnet" "other" { + vpc_id = "${aws_vpc.foo.id}" + availability_zone = "us-west-2b" + cidr_block = "10.1.2.0/24" + tags { + Name = "tf-acc-db-instance-mssql-timezone-other" + } + } + + resource "aws_db_instance" "bar" { + identifier = "foobarbaz-test-terraform-%d" + + db_subnet_group_name = "${aws_db_subnet_group.rds_one.name}" + allocated_storage = 10 + engine = "MySQL" + engine_version = "5.6" + instance_class = "db.t2.micro" + name = "baz" + password = "barbarbarbar" + username = "foo" + skip_final_snapshot = true + + enabled_cloudwatch_logs_exports = [ + "audit", + "error", + ] + } + `, rInt, rInt) +} + +func testAccAWSDBInstanceConfigCloudwatchLogsExportConfigurationAdd(rInt int) string { + return fmt.Sprintf(` + + resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" + enable_dns_hostnames = true + tags { + Name = "terraform-testacc-db-instance-mssql-timezone" + } + } + + resource "aws_db_subnet_group" "rds_one" { + name = "tf_acc_test_%d" + description = "db subnets for rds_one" + + subnet_ids = ["${aws_subnet.main.id}", "${aws_subnet.other.id}"] + } + + resource "aws_subnet" "main" { + vpc_id = "${aws_vpc.foo.id}" + availability_zone = "us-west-2a" + cidr_block = "10.1.1.0/24" + tags { + Name = "tf-acc-db-instance-mssql-timezone-main" + } + } + + resource "aws_subnet" "other" { + vpc_id = "${aws_vpc.foo.id}" + availability_zone = "us-west-2b" + cidr_block = "10.1.2.0/24" + tags { + Name = "tf-acc-db-instance-mssql-timezone-other" + } + } + + resource "aws_db_instance" "bar" { + identifier = "foobarbaz-test-terraform-%d" + + db_subnet_group_name = "${aws_db_subnet_group.rds_one.name}" + allocated_storage = 10 + engine = "MySQL" + engine_version = "5.6" + instance_class = "db.t2.micro" + name = "baz" + password = "barbarbarbar" + username = "foo" + skip_final_snapshot = true + + apply_immediately = true + + enabled_cloudwatch_logs_exports = [ + "audit", + "error", + "general", + ] + } + `, rInt, rInt) +} + +func testAccAWSDBInstanceConfigCloudwatchLogsExportConfigurationModify(rInt int) string { + return fmt.Sprintf(` + + resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" + enable_dns_hostnames = true + tags { + Name = "terraform-testacc-db-instance-mssql-timezone" + } + } + + resource "aws_db_subnet_group" "rds_one" { + name = "tf_acc_test_%d" + description = "db subnets for rds_one" + + subnet_ids = ["${aws_subnet.main.id}", "${aws_subnet.other.id}"] + } + + resource "aws_subnet" "main" { + vpc_id = "${aws_vpc.foo.id}" + availability_zone = "us-west-2a" + cidr_block = "10.1.1.0/24" + tags { + Name = "tf-acc-db-instance-mssql-timezone-main" + } + } + + resource "aws_subnet" "other" { + vpc_id = "${aws_vpc.foo.id}" + availability_zone = "us-west-2b" + cidr_block = "10.1.2.0/24" + tags { + Name = "tf-acc-db-instance-mssql-timezone-other" + } + } + + resource "aws_db_instance" "bar" { + identifier = "foobarbaz-test-terraform-%d" + + db_subnet_group_name = "${aws_db_subnet_group.rds_one.name}" + allocated_storage = 10 + engine = "MySQL" + engine_version = "5.6" + instance_class = "db.t2.micro" + name = "baz" + password = "barbarbarbar" + username = "foo" + skip_final_snapshot = true + + apply_immediately = true + + enabled_cloudwatch_logs_exports = [ + "audit", + "general", + "slowquery", + ] + } + `, rInt, rInt) +} + +func testAccAWSDBInstanceConfigCloudwatchLogsExportConfigurationDelete(rInt int) string { + return fmt.Sprintf(` + + resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" + enable_dns_hostnames = true + tags { + Name = "terraform-testacc-db-instance-mssql-timezone" + } + } + + resource "aws_db_subnet_group" "rds_one" { + name = "tf_acc_test_%d" + description = "db subnets for rds_one" + + subnet_ids = ["${aws_subnet.main.id}", "${aws_subnet.other.id}"] + } + + resource "aws_subnet" "main" { + vpc_id = "${aws_vpc.foo.id}" + availability_zone = "us-west-2a" + cidr_block = "10.1.1.0/24" + tags { + Name = "tf-acc-db-instance-mssql-timezone-main" + } + } + + resource "aws_subnet" "other" { + vpc_id = "${aws_vpc.foo.id}" + availability_zone = "us-west-2b" + cidr_block = "10.1.2.0/24" + tags { + Name = "tf-acc-db-instance-mssql-timezone-other" + } + } + + resource "aws_db_instance" "bar" { + identifier = "foobarbaz-test-terraform-%d" + + db_subnet_group_name = "${aws_db_subnet_group.rds_one.name}" + allocated_storage = 10 + engine = "MySQL" + engine_version = "5.6" + instance_class = "db.t2.micro" + name = "baz" + password = "barbarbarbar" + username = "foo" + skip_final_snapshot = true + + apply_immediately = true + + } + `, rInt, rInt) +} + func testAccAWSDBInstanceConfigEc2Classic(rInt int) string { return fmt.Sprintf(` resource "aws_db_instance" "bar" { From df587f3ae4124f96b9774bfa0d468e554dad991a Mon Sep 17 00:00:00 2001 From: Kentaro Terada Date: Sat, 7 Apr 2018 23:56:58 +0900 Subject: [PATCH 2/2] Update db_instance resources docs --- website/docs/r/db_instance.html.markdown | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/website/docs/r/db_instance.html.markdown b/website/docs/r/db_instance.html.markdown index 96d55357a80..f9e64bae8d4 100644 --- a/website/docs/r/db_instance.html.markdown +++ b/website/docs/r/db_instance.html.markdown @@ -87,6 +87,8 @@ Default is `false`. * `db_subnet_group_name` - (Optional) Name of DB subnet group. DB instance will be created in the VPC associated with the DB subnet group. If unspecified, will be created in the `default` VPC, or in EC2 Classic, if available. +* `enabled_cloudwatch_logs_exports` - (Optional) Name list of enable log type for exporting to cloudwatch logs. If omitted, any logs will not be exported to cloudwatch logs. + Either of the following is supported: `audit`, `error`, `general`, `slowquery`. * `engine` - (Required unless a `snapshot_identifier` or `replicate_source_db` is provided) The database engine to use. * `engine_version` - (Optional) The engine version to use. If `auto_minor_version_upgrade` @@ -135,9 +137,9 @@ logs, and it will be stored in the state file. accessible. Default is `false`. * `replicate_source_db` - (Optional) Specifies that this resource is a Replicate database, and to use this value as the source database. This correlates to the -`identifier` of another Amazon RDS Database to replicate. Note that if you are +`identifier` of another Amazon RDS Database to replicate. Note that if you are creating a cross-region replica of an encrypted database you will also need to -specify a `kms_key_id`. See [DB Instance Replication][1] and [Working with +specify a `kms_key_id`. See [DB Instance Replication][1] and [Working with PostgreSQL and MySQL Read Replicas](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_ReadRepl.html) for more information on using Replication. * `security_group_names` - (Optional/Deprecated) List of DB Security Groups to @@ -151,9 +153,9 @@ is `false`. * `snapshot_identifier` - (Optional) Specifies whether or not to create this database from a snapshot. This correlates to the snapshot ID you'd find in the RDS console, e.g: rds:production-2015-06-26-06-05. -* `storage_encrypted` - (Optional) Specifies whether the DB instance is -encrypted. Note that if you are creating a cross-region read replica this field -is ignored and you should instead declare `kms_key_id` with a valid ARN. The +* `storage_encrypted` - (Optional) Specifies whether the DB instance is +encrypted. Note that if you are creating a cross-region read replica this field +is ignored and you should instead declare `kms_key_id` with a valid ARN. The default is `false` if not specified. * `storage_type` - (Optional) One of "standard" (magnetic), "gp2" (general purpose SSD), or "io1" (provisioned IOPS SSD). The default is "io1" if `iops` is