Skip to content

Commit

Permalink
Add support for DynamoDb as an AWS DMS target (#1002)
Browse files Browse the repository at this point in the history
* Added support for dynamodb as dms endpoint (with tests). For issue: (hashicorp/terraform#14752)

Signed-off-by: stack72 <[email protected]>

* resource/aws_dms_endpoint: Add retry to allow IAM to propagate

Allows the IAM Role for DynamoDB to propagate for use in DMS Endpoint
  • Loading branch information
stack72 authored Jun 29, 2017
1 parent ac8d0b7 commit c82b217
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 29 deletions.
84 changes: 65 additions & 19 deletions aws/resource_aws_dms_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package aws
import (
"log"
"strings"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
dms "github.com/aws/aws-sdk-go/service/databasemigrationservice"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
)
Expand Down Expand Up @@ -43,6 +45,10 @@ func resourceAwsDmsEndpoint() *schema.Resource {
ForceNew: true,
ValidateFunc: validateDmsEndpointId,
},
"service_access_role": {
Type: schema.TypeString,
Optional: true,
},
"endpoint_type": {
Type: schema.TypeString,
Required: true,
Expand All @@ -58,6 +64,7 @@ func resourceAwsDmsEndpoint() *schema.Resource {
"mysql",
"oracle",
"postgres",
"dynamodb",
"mariadb",
"aurora",
"redshift",
Expand All @@ -79,16 +86,16 @@ func resourceAwsDmsEndpoint() *schema.Resource {
},
"password": {
Type: schema.TypeString,
Required: true,
Optional: true,
Sensitive: true,
},
"port": {
Type: schema.TypeInt,
Required: true,
Optional: true,
},
"server_name": {
Type: schema.TypeString,
Required: true,
Optional: true,
},
"ssl_mode": {
Type: schema.TypeString,
Expand All @@ -107,7 +114,7 @@ func resourceAwsDmsEndpoint() *schema.Resource {
},
"username": {
Type: schema.TypeString,
Required: true,
Optional: true,
},
},
}
Expand All @@ -120,22 +127,31 @@ func resourceAwsDmsEndpointCreate(d *schema.ResourceData, meta interface{}) erro
EndpointIdentifier: aws.String(d.Get("endpoint_id").(string)),
EndpointType: aws.String(d.Get("endpoint_type").(string)),
EngineName: aws.String(d.Get("engine_name").(string)),
Password: aws.String(d.Get("password").(string)),
Port: aws.Int64(int64(d.Get("port").(int))),
ServerName: aws.String(d.Get("server_name").(string)),
Tags: dmsTagsFromMap(d.Get("tags").(map[string]interface{})),
Username: aws.String(d.Get("username").(string)),
}

if v, ok := d.GetOk("database_name"); ok {
request.DatabaseName = aws.String(v.(string))
// if dynamodb then add required params
if d.Get("engine_name").(string) == "dynamodb" {
request.DynamoDbSettings = &dms.DynamoDbSettings{
ServiceAccessRoleArn: aws.String(d.Get("service_access_role").(string)),
}
} else {
request.Password = aws.String(d.Get("password").(string))
request.Port = aws.Int64(int64(d.Get("port").(int)))
request.ServerName = aws.String(d.Get("server_name").(string))
request.Username = aws.String(d.Get("username").(string))

if v, ok := d.GetOk("database_name"); ok {
request.DatabaseName = aws.String(v.(string))
}
if v, ok := d.GetOk("extra_connection_attributes"); ok {
request.ExtraConnectionAttributes = aws.String(v.(string))
}
}

if v, ok := d.GetOk("certificate_arn"); ok {
request.CertificateArn = aws.String(v.(string))
}
if v, ok := d.GetOk("extra_connection_attributes"); ok {
request.ExtraConnectionAttributes = aws.String(v.(string))
}
if v, ok := d.GetOk("kms_key_arn"); ok {
request.KmsKeyId = aws.String(v.(string))
}
Expand All @@ -145,7 +161,20 @@ func resourceAwsDmsEndpointCreate(d *schema.ResourceData, meta interface{}) erro

log.Println("[DEBUG] DMS create endpoint:", request)

_, err := conn.CreateEndpoint(request)
err := resource.Retry(5*time.Minute, func() *resource.RetryError {
if _, err := conn.CreateEndpoint(request); err != nil {
if awserr, ok := err.(awserr.Error); ok {
switch awserr.Code() {
case "AccessDeniedFault":
return resource.RetryableError(awserr)
}
}
// Didn't recognize the error, so shouldn't retry.
return resource.NonRetryableError(err)
}
// Successful delete
return nil
})
if err != nil {
return err
}
Expand Down Expand Up @@ -208,6 +237,13 @@ func resourceAwsDmsEndpointUpdate(d *schema.ResourceData, meta interface{}) erro
hasChanges = true
}

if d.HasChange("service_access_role") {
request.DynamoDbSettings = &dms.DynamoDbSettings{
ServiceAccessRoleArn: aws.String(d.Get("service_access_role").(string)),
}
hasChanges = true
}

if d.HasChange("endpoint_type") {
request.EndpointType = aws.String(d.Get("endpoint_type").(string))
hasChanges = true
Expand Down Expand Up @@ -290,18 +326,28 @@ func resourceAwsDmsEndpointSetState(d *schema.ResourceData, endpoint *dms.Endpoi
d.SetId(*endpoint.EndpointIdentifier)

d.Set("certificate_arn", endpoint.CertificateArn)
d.Set("database_name", endpoint.DatabaseName)
d.Set("endpoint_arn", endpoint.EndpointArn)
d.Set("endpoint_id", endpoint.EndpointIdentifier)
// For some reason the AWS API only accepts lowercase type but returns it as uppercase
d.Set("endpoint_type", strings.ToLower(*endpoint.EndpointType))
d.Set("engine_name", endpoint.EngineName)
d.Set("extra_connection_attributes", endpoint.ExtraConnectionAttributes)

if *endpoint.EngineName == "dynamodb" {
if endpoint.DynamoDbSettings != nil {
d.Set("service_access_role", endpoint.DynamoDbSettings.ServiceAccessRoleArn)
} else {
d.Set("service_access_role", "")
}
} else {
d.Set("database_name", endpoint.DatabaseName)
d.Set("extra_connection_attributes", endpoint.ExtraConnectionAttributes)
d.Set("port", endpoint.Port)
d.Set("server_name", endpoint.ServerName)
d.Set("username", endpoint.Username)
}

d.Set("kms_key_arn", endpoint.KmsKeyId)
d.Set("port", endpoint.Port)
d.Set("server_name", endpoint.ServerName)
d.Set("ssl_mode", endpoint.SslMode)
d.Set("username", endpoint.Username)

return nil
}
165 changes: 160 additions & 5 deletions aws/resource_aws_dms_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ import (

func TestAccAwsDmsEndpointBasic(t *testing.T) {
resourceName := "aws_dms_endpoint.dms_endpoint"
randId := acctest.RandString(8)
randId := acctest.RandString(8) + "-basic"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: dmsEndpointDestroy,
Steps: []resource.TestStep{
{
Config: dmsEndpointConfig(randId),
Config: dmsEndpointBasicConfig(randId),
Check: resource.ComposeTestCheckFunc(
checkDmsEndpointExists(resourceName),
resource.TestCheckResourceAttrSet(resourceName, "endpoint_arn"),
Expand All @@ -34,7 +34,7 @@ func TestAccAwsDmsEndpointBasic(t *testing.T) {
ImportStateVerifyIgnore: []string{"password"},
},
{
Config: dmsEndpointConfigUpdate(randId),
Config: dmsEndpointBasicConfigUpdate(randId),
Check: resource.ComposeTestCheckFunc(
checkDmsEndpointExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "database_name", "tf-test-dms-db-updated"),
Expand All @@ -50,6 +50,38 @@ func TestAccAwsDmsEndpointBasic(t *testing.T) {
})
}

func TestAccAwsDmsEndpointDynamoDb(t *testing.T) {
resourceName := "aws_dms_endpoint.dms_endpoint"
randId := acctest.RandString(8) + "-dynamodb"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: dmsEndpointDestroy,
Steps: []resource.TestStep{
{
Config: dmsEndpointDynamoDbConfig(randId),
Check: resource.ComposeTestCheckFunc(
checkDmsEndpointExists(resourceName),
resource.TestCheckResourceAttrSet(resourceName, "endpoint_arn"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"password"},
},
{
Config: dmsEndpointDynamoDbConfigUpdate(randId),
Check: resource.ComposeTestCheckFunc(
checkDmsEndpointExists(resourceName),
),
},
},
})
}

func dmsEndpointDestroy(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_dms_endpoint" {
Expand Down Expand Up @@ -98,7 +130,7 @@ func checkDmsEndpointExists(n string) resource.TestCheckFunc {
}
}

func dmsEndpointConfig(randId string) string {
func dmsEndpointBasicConfig(randId string) string {
return fmt.Sprintf(`
resource "aws_dms_endpoint" "dms_endpoint" {
database_name = "tf-test-dms-db"
Expand All @@ -120,7 +152,7 @@ resource "aws_dms_endpoint" "dms_endpoint" {
`, randId)
}

func dmsEndpointConfigUpdate(randId string) string {
func dmsEndpointBasicConfigUpdate(randId string) string {
return fmt.Sprintf(`
resource "aws_dms_endpoint" "dms_endpoint" {
database_name = "tf-test-dms-db-updated"
Expand All @@ -141,3 +173,126 @@ resource "aws_dms_endpoint" "dms_endpoint" {
}
`, randId)
}

func dmsEndpointDynamoDbConfig(randId string) string {
return fmt.Sprintf(`
resource "aws_dms_endpoint" "dms_endpoint" {
endpoint_id = "tf-test-dms-endpoint-%[1]s"
endpoint_type = "target"
engine_name = "dynamodb"
service_access_role = "${aws_iam_role.iam_role.arn}"
ssl_mode = "none"
tags {
Name = "tf-test-dynamodb-endpoint-%[1]s"
Update = "to-update"
Remove = "to-remove"
}
depends_on = ["aws_iam_role_policy.dms_dynamodb_access"]
}
resource "aws_iam_role" "iam_role" {
name = "tf-test-iam-dynamodb-role-%[1]s"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "dms.amazonaws.com"
},
"Effect": "Allow"
}
]
}
EOF
}
resource "aws_iam_role_policy" "dms_dynamodb_access" {
name = "tf-test-iam-dynamodb-role-policy-%[1]s"
role = "${aws_iam_role.iam_role.name}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:PutItem",
"dynamodb:CreateTable",
"dynamodb:DescribeTable",
"dynamodb:DeleteTable",
"dynamodb:DeleteItem",
"dynamodb:ListTables"
],
"Resource": "*"
}
]
}
EOF
}
`, randId)
}

func dmsEndpointDynamoDbConfigUpdate(randId string) string {
return fmt.Sprintf(`
resource "aws_dms_endpoint" "dms_endpoint" {
endpoint_id = "tf-test-dms-endpoint-%[1]s"
endpoint_type = "target"
engine_name = "dynamodb"
service_access_role = "${aws_iam_role.iam_role.arn}"
ssl_mode = "none"
tags {
Name = "tf-test-dynamodb-endpoint-%[1]s"
Update = "updated"
Add = "added"
}
}
resource "aws_iam_role" "iam_role" {
name = "tf-test-iam-dynamodb-role-%[1]s"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "dms.amazonaws.com"
},
"Effect": "Allow"
}
]
}
EOF
}
resource "aws_iam_role_policy" "dms_dynamodb_access" {
name = "tf-test-iam-dynamodb-role-policy-%[1]s"
role = "${aws_iam_role.iam_role.name}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:PutItem",
"dynamodb:CreateTable",
"dynamodb:DescribeTable",
"dynamodb:DeleteTable",
"dynamodb:DeleteItem",
"dynamodb:ListTables"
],
"Resource": "*"
}
]
}
EOF
}
`, randId)
}
Loading

0 comments on commit c82b217

Please sign in to comment.