provider/aws: Add AWS DMS (data migration service) resources (#11122)

* Add aws dms vendoring

* Add aws dms endpoint resource

* Add aws dms replication instance resource

* Add aws dms replication subnet group resource

* Add aws dms replication task resource

* Fix aws dms resource go vet errors

* Review fixes: Add id validators for all resources. Add validator for endpoint engine_name.

* Add aws dms resources to importability list

* Review fixes: Add aws dms iam role dependencies to test cases

* Review fixes: Adjustments for handling input values

* Add aws dms replication subnet group tagging

* Fix aws dms subnet group doesn't use standard error for resource not found

* Missed update of aws dms vendored version

* Add aws dms certificate resource

* Update aws dms resources to force new for immutable attributes

* Fix tests failing on subnet deletion by adding explicit dependencies. Combine import tests with basic tests to cut down runtime.
This commit is contained in:
Jack Bruno 2017-02-02 03:30:05 -07:00 committed by Paul Stack
parent b30ef0f58d
commit be56c7d2f6
26 changed files with 10480 additions and 0 deletions

View File

@ -26,6 +26,7 @@ import (
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/aws/aws-sdk-go/service/codecommit"
"github.com/aws/aws-sdk-go/service/codedeploy"
"github.com/aws/aws-sdk-go/service/databasemigrationservice"
"github.com/aws/aws-sdk-go/service/directoryservice"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/ec2"
@ -106,6 +107,7 @@ type AWSClient struct {
cloudwatchconn *cloudwatch.CloudWatch
cloudwatchlogsconn *cloudwatchlogs.CloudWatchLogs
cloudwatcheventsconn *cloudwatchevents.CloudWatchEvents
dmsconn *databasemigrationservice.DatabaseMigrationService
dsconn *directoryservice.DirectoryService
dynamodbconn *dynamodb.DynamoDB
ec2conn *ec2.EC2
@ -276,6 +278,7 @@ func (c *Config) Client() (interface{}, error) {
client.cloudwatchlogsconn = cloudwatchlogs.New(sess)
client.codecommitconn = codecommit.New(sess)
client.codedeployconn = codedeploy.New(sess)
client.dmsconn = databasemigrationservice.New(sess)
client.dsconn = directoryservice.New(sess)
client.dynamodbconn = dynamodb.New(dynamoSess)
client.ec2conn = ec2.New(awsEc2Sess)

View File

@ -246,6 +246,11 @@ func Provider() terraform.ResourceProvider {
"aws_db_security_group": resourceAwsDbSecurityGroup(),
"aws_db_subnet_group": resourceAwsDbSubnetGroup(),
"aws_directory_service_directory": resourceAwsDirectoryServiceDirectory(),
"aws_dms_certificate": resourceAwsDmsCertificate(),
"aws_dms_endpoint": resourceAwsDmsEndpoint(),
"aws_dms_replication_instance": resourceAwsDmsReplicationInstance(),
"aws_dms_replication_subnet_group": resourceAwsDmsReplicationSubnetGroup(),
"aws_dms_replication_task": resourceAwsDmsReplicationTask(),
"aws_dynamodb_table": resourceAwsDynamoDbTable(),
"aws_ebs_snapshot": resourceAwsEbsSnapshot(),
"aws_ebs_volume": resourceAwsEbsVolume(),

View File

@ -0,0 +1,138 @@
package aws
import (
"fmt"
"log"
"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/schema"
)
func resourceAwsDmsCertificate() *schema.Resource {
return &schema.Resource{
Create: resourceAwsDmsCertificateCreate,
Read: resourceAwsDmsCertificateRead,
Delete: resourceAwsDmsCertificateDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"certificate_arn": {
Type: schema.TypeString,
Computed: true,
},
"certificate_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateDmsCertificateId,
},
"certificate_pem": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Sensitive: true,
},
"certificate_wallet": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Sensitive: true,
},
},
}
}
func resourceAwsDmsCertificateCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn
request := &dms.ImportCertificateInput{
CertificateIdentifier: aws.String(d.Get("certificate_id").(string)),
}
pem, pemSet := d.GetOk("certificate_pem")
wallet, walletSet := d.GetOk("certificate_wallet")
if !pemSet && !walletSet {
return fmt.Errorf("Must set either certificate_pem and certificate_wallet.")
}
if pemSet && walletSet {
return fmt.Errorf("Cannot set both certificate_pem and certificate_wallet.")
}
if pemSet {
request.CertificatePem = aws.String(pem.(string))
}
if walletSet {
request.CertificateWallet = []byte(wallet.(string))
}
log.Println("[DEBUG] DMS import certificate:", request)
_, err := conn.ImportCertificate(request)
if err != nil {
return err
}
d.SetId(d.Get("certificate_id").(string))
return resourceAwsDmsCertificateRead(d, meta)
}
func resourceAwsDmsCertificateRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn
response, err := conn.DescribeCertificates(&dms.DescribeCertificatesInput{
Filters: []*dms.Filter{
{
Name: aws.String("certificate-id"),
Values: []*string{aws.String(d.Id())}, // Must use d.Id() to work with import.
},
},
})
if err != nil {
if dmserr, ok := err.(awserr.Error); ok && dmserr.Code() == "ResourceNotFoundFault" {
d.SetId("")
return nil
}
return err
}
return resourceAwsDmsCertificateSetState(d, response.Certificates[0])
}
func resourceAwsDmsCertificateDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn
request := &dms.DeleteCertificateInput{
CertificateArn: aws.String(d.Get("certificate_arn").(string)),
}
log.Printf("[DEBUG] DMS delete certificate: %#v", request)
_, err := conn.DeleteCertificate(request)
if err != nil {
return err
}
return nil
}
func resourceAwsDmsCertificateSetState(d *schema.ResourceData, cert *dms.Certificate) error {
d.SetId(*cert.CertificateIdentifier)
d.Set("certificate_id", cert.CertificateIdentifier)
d.Set("certificate_arn", cert.CertificateArn)
if cert.CertificatePem != nil && *cert.CertificatePem != "" {
d.Set("certificate_pem", cert.CertificatePem)
}
if cert.CertificateWallet != nil && len(cert.CertificateWallet) == 0 {
d.Set("certificate_wallet", cert.CertificateWallet)
}
return nil
}

View File

@ -0,0 +1,103 @@
package aws
import (
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
dms "github.com/aws/aws-sdk-go/service/databasemigrationservice"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAwsDmsCertificateBasic(t *testing.T) {
resourceName := "aws_dms_certificate.dms_certificate"
randId := acctest.RandString(8)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: dmsCertificateDestroy,
Steps: []resource.TestStep{
{
Config: dmsCertificateConfig(randId),
Check: resource.ComposeTestCheckFunc(
checkDmsCertificateExists(resourceName),
resource.TestCheckResourceAttrSet(resourceName, "certificate_arn"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func dmsCertificateDestroy(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_dms_certificate" {
continue
}
err := checkDmsCertificateExists(rs.Primary.ID)
if err == nil {
return fmt.Errorf("Found a certificate that was not destroyed: %s", rs.Primary.ID)
}
}
return nil
}
func checkDmsCertificateExists(n string) resource.TestCheckFunc {
providers := []*schema.Provider{testAccProvider}
return checkDmsCertificateExistsWithProviders(n, &providers)
}
func checkDmsCertificateExistsWithProviders(n string, providers *[]*schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
for _, provider := range *providers {
// Ignore if Meta is empty, this can happen for validation providers
if provider.Meta() == nil {
continue
}
conn := provider.Meta().(*AWSClient).dmsconn
_, err := conn.DescribeCertificates(&dms.DescribeCertificatesInput{
Filters: []*dms.Filter{
{
Name: aws.String("certificate-id"),
Values: []*string{aws.String(rs.Primary.ID)},
},
},
})
if err != nil {
return fmt.Errorf("DMS certificate error: %v", err)
}
return nil
}
return fmt.Errorf("DMS certificate not found")
}
}
func dmsCertificateConfig(randId string) string {
return fmt.Sprintf(`
resource "aws_dms_certificate" "dms_certificate" {
certificate_id = "tf-test-dms-certificate-%[1]s"
certificate_pem = "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIJAJ58TJVjU7G1MA0GCSqGSIb3DQEBBQUAMFExCzAJBgNV\nBAYTAlVTMREwDwYDVQQIEwhDb2xvcmFkbzEPMA0GA1UEBxMGRGVudmVyMRAwDgYD\nVQQKEwdDaGFydGVyMQwwCgYDVQQLEwNDU0UwHhcNMTcwMTMwMTkyMDA4WhcNMjYx\nMjA5MTkyMDA4WjBRMQswCQYDVQQGEwJVUzERMA8GA1UECBMIQ29sb3JhZG8xDzAN\nBgNVBAcTBkRlbnZlcjEQMA4GA1UEChMHQ2hhcnRlcjEMMAoGA1UECxMDQ1NFMIIB\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv6dq6VLIImlAaTrckb5w3X6J\nWP7EGz2ChGAXlkEYto6dPCba0v5+f+8UlMOpeB25XGoai7gdItqNWVFpYsgmndx3\nvTad3ukO1zeElKtw5oHPH2plOaiv/gVJaDa9NTeINj0EtGZs74fCOclAzGFX5vBc\nb08ESWBceRgGjGv3nlij4JzHfqTkCKQz6P6pBivQBfk62rcOkkH5rKoaGltRHROS\nMbkwOhu2hN0KmSYTXRvts0LXnZU4N0l2ms39gmr7UNNNlKYINL2JoTs9dNBc7APD\ndZvlEHd+/FjcLCI8hC3t4g4AbfW0okIBCNG0+oVjqGb2DeONSJKsThahXt89MQID\nAQABo4G0MIGxMB0GA1UdDgQWBBQKq8JxjY1GmeZXJjfOMfW0kBIzPDCBgQYDVR0j\nBHoweIAUCqvCcY2NRpnmVyY3zjH1tJASMzyhVaRTMFExCzAJBgNVBAYTAlVTMREw\nDwYDVQQIEwhDb2xvcmFkbzEPMA0GA1UEBxMGRGVudmVyMRAwDgYDVQQKEwdDaGFy\ndGVyMQwwCgYDVQQLEwNDU0WCCQCefEyVY1OxtTAMBgNVHRMEBTADAQH/MA0GCSqG\nSIb3DQEBBQUAA4IBAQAWifoMk5kbv+yuWXvFwHiB4dWUUmMlUlPU/E300yVTRl58\np6DfOgJs7MMftd1KeWqTO+uW134QlTt7+jwI8Jq0uyKCu/O2kJhVtH/Ryog14tGl\n+wLcuIPLbwJI9CwZX4WMBrq4DnYss+6F47i8NCc+Z3MAiG4vtq9ytBmaod0dj2bI\ng4/Lac0e00dql9RnqENh1+dF0V+QgTJCoPkMqDNAlSB8vOodBW81UAb2z12t+IFi\n3X9J3WtCK2+T5brXL6itzewWJ2ALvX3QpmZx7fMHJ3tE+SjjyivE1BbOlzYHx83t\nTeYnm7pS9un7A/UzTDHbs7hPUezLek+H3xTPAnnq\n-----END CERTIFICATE-----\n"
}
`, randId)
}

View File

@ -0,0 +1,343 @@
package aws
import (
"log"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/private/waiter"
dms "github.com/aws/aws-sdk-go/service/databasemigrationservice"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
)
func resourceAwsDmsEndpoint() *schema.Resource {
return &schema.Resource{
Create: resourceAwsDmsEndpointCreate,
Read: resourceAwsDmsEndpointRead,
Update: resourceAwsDmsEndpointUpdate,
Delete: resourceAwsDmsEndpointDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"certificate_arn": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ValidateFunc: validateArn,
},
"database_name": {
Type: schema.TypeString,
Required: true,
},
"endpoint_arn": {
Type: schema.TypeString,
Computed: true,
},
"endpoint_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateDmsEndpointId,
},
"endpoint_type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
"source",
"target",
}, false),
},
"engine_name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
"mysql",
"oracle",
"postgres",
"mariadb",
"aurora",
"redshift",
"sybase",
"sqlserver",
}, false),
},
"extra_connection_attributes": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"kms_key_arn": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
ValidateFunc: validateArn,
},
"password": {
Type: schema.TypeString,
Required: true,
Sensitive: true,
},
"port": {
Type: schema.TypeInt,
Required: true,
},
"server_name": {
Type: schema.TypeString,
Required: true,
},
"ssl_mode": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{
"none",
"require",
"verify-ca",
"verify-full",
}, false),
},
"tags": {
Type: schema.TypeMap,
Optional: true,
},
"username": {
Type: schema.TypeString,
Required: true,
},
},
}
}
func resourceAwsDmsEndpointCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn
request := &dms.CreateEndpointInput{
DatabaseName: aws.String(d.Get("database_name").(string)),
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("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))
}
if v, ok := d.GetOk("ssl_mode"); ok {
request.SslMode = aws.String(v.(string))
}
log.Println("[DEBUG] DMS create endpoint:", request)
_, err := conn.CreateEndpoint(request)
if err != nil {
return err
}
d.SetId(d.Get("endpoint_id").(string))
return resourceAwsDmsEndpointRead(d, meta)
}
func resourceAwsDmsEndpointRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn
response, err := conn.DescribeEndpoints(&dms.DescribeEndpointsInput{
Filters: []*dms.Filter{
{
Name: aws.String("endpoint-id"),
Values: []*string{aws.String(d.Id())}, // Must use d.Id() to work with import.
},
},
})
if err != nil {
if dmserr, ok := err.(awserr.Error); ok && dmserr.Code() == "ResourceNotFoundFault" {
d.SetId("")
return nil
}
return err
}
err = resourceAwsDmsEndpointSetState(d, response.Endpoints[0])
if err != nil {
return err
}
tagsResp, err := conn.ListTagsForResource(&dms.ListTagsForResourceInput{
ResourceArn: aws.String(d.Get("endpoint_arn").(string)),
})
if err != nil {
return err
}
d.Set("tags", dmsTagsToMap(tagsResp.TagList))
return nil
}
func resourceAwsDmsEndpointUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn
request := &dms.ModifyEndpointInput{
EndpointArn: aws.String(d.Get("endpoint_arn").(string)),
}
hasChanges := false
if d.HasChange("certificate_arn") {
request.CertificateArn = aws.String(d.Get("certificate_arn").(string))
hasChanges = true
}
if d.HasChange("database_name") {
request.DatabaseName = aws.String(d.Get("database_name").(string))
hasChanges = true
}
if d.HasChange("endpoint_type") {
request.EndpointType = aws.String(d.Get("endpoint_type").(string))
hasChanges = true
}
if d.HasChange("engine_name") {
request.EngineName = aws.String(d.Get("engine_name").(string))
hasChanges = true
}
if d.HasChange("extra_connection_attributes") {
request.ExtraConnectionAttributes = aws.String(d.Get("extra_connection_attributes").(string))
hasChanges = true
}
if d.HasChange("password") {
request.Password = aws.String(d.Get("password").(string))
hasChanges = true
}
if d.HasChange("port") {
request.Port = aws.Int64(int64(d.Get("port").(int)))
hasChanges = true
}
if d.HasChange("server_name") {
request.ServerName = aws.String(d.Get("server_name").(string))
hasChanges = true
}
if d.HasChange("ssl_mode") {
request.SslMode = aws.String(d.Get("ssl_mode").(string))
hasChanges = true
}
if d.HasChange("username") {
request.Username = aws.String(d.Get("username").(string))
hasChanges = true
}
if d.HasChange("tags") {
err := dmsSetTags(d.Get("endpoint_arn").(string), d, meta)
if err != nil {
return err
}
}
if hasChanges {
log.Println("[DEBUG] DMS update endpoint:", request)
_, err := conn.ModifyEndpoint(request)
if err != nil {
return err
}
return resourceAwsDmsEndpointRead(d, meta)
}
return nil
}
func resourceAwsDmsEndpointDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn
request := &dms.DeleteEndpointInput{
EndpointArn: aws.String(d.Get("endpoint_arn").(string)),
}
log.Printf("[DEBUG] DMS delete endpoint: %#v", request)
_, err := conn.DeleteEndpoint(request)
if err != nil {
return err
}
waitErr := waitForEndpointDelete(conn, d.Get("endpoint_id").(string), 30, 20)
if waitErr != nil {
return waitErr
}
return nil
}
func resourceAwsDmsEndpointSetState(d *schema.ResourceData, endpoint *dms.Endpoint) error {
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)
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
}
func waitForEndpointDelete(client *dms.DatabaseMigrationService, endpointId string, delay int, maxAttempts int) error {
input := &dms.DescribeEndpointsInput{
Filters: []*dms.Filter{
{
Name: aws.String("endpoint-id"),
Values: []*string{aws.String(endpointId)},
},
},
}
config := waiter.Config{
Operation: "DescribeEndpoints",
Delay: delay,
MaxAttempts: maxAttempts,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "path",
Argument: "length(Endpoints[]) > `0`",
Expected: false,
},
},
}
w := waiter.Waiter{
Client: client,
Input: input,
Config: config,
}
return w.Wait()
}

View File

@ -0,0 +1,152 @@
package aws
import (
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
dms "github.com/aws/aws-sdk-go/service/databasemigrationservice"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAwsDmsEndpointBasic(t *testing.T) {
resourceName := "aws_dms_endpoint.dms_endpoint"
randId := acctest.RandString(8)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: dmsEndpointDestroy,
Steps: []resource.TestStep{
{
Config: dmsEndpointConfig(randId),
Check: resource.ComposeTestCheckFunc(
checkDmsEndpointExists(resourceName),
resource.TestCheckResourceAttrSet(resourceName, "endpoint_arn"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"password"},
},
{
Config: dmsEndpointConfigUpdate(randId),
Check: resource.ComposeTestCheckFunc(
checkDmsEndpointExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "database_name", "tf-test-dms-db-updated"),
resource.TestCheckResourceAttr(resourceName, "extra_connection_attributes", "extra"),
resource.TestCheckResourceAttr(resourceName, "password", "tftestupdate"),
resource.TestCheckResourceAttr(resourceName, "port", "3303"),
resource.TestCheckResourceAttr(resourceName, "ssl_mode", "none"),
resource.TestCheckResourceAttr(resourceName, "server_name", "tftestupdate"),
resource.TestCheckResourceAttr(resourceName, "username", "tftestupdate"),
),
},
},
})
}
func dmsEndpointDestroy(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_dms_endpoint" {
continue
}
err := checkDmsEndpointExists(rs.Primary.ID)
if err == nil {
return fmt.Errorf("Found an endpoint that was not destroyed: %s", rs.Primary.ID)
}
}
return nil
}
func checkDmsEndpointExists(n string) resource.TestCheckFunc {
providers := []*schema.Provider{testAccProvider}
return checkDmsEndpointExistsWithProviders(n, &providers)
}
func checkDmsEndpointExistsWithProviders(n string, providers *[]*schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
for _, provider := range *providers {
// Ignore if Meta is empty, this can happen for validation providers
if provider.Meta() == nil {
continue
}
conn := provider.Meta().(*AWSClient).dmsconn
_, err := conn.DescribeEndpoints(&dms.DescribeEndpointsInput{
Filters: []*dms.Filter{
{
Name: aws.String("endpoint-id"),
Values: []*string{aws.String(rs.Primary.ID)},
},
},
})
if err != nil {
return fmt.Errorf("DMS endpoint error: %v", err)
}
return nil
}
return fmt.Errorf("DMS endpoint not found")
}
}
func dmsEndpointConfig(randId string) string {
return fmt.Sprintf(`
resource "aws_dms_endpoint" "dms_endpoint" {
database_name = "tf-test-dms-db"
endpoint_id = "tf-test-dms-endpoint-%[1]s"
endpoint_type = "source"
engine_name = "aurora"
extra_connection_attributes = ""
password = "tftest"
port = 3306
server_name = "tftest"
ssl_mode = "none"
tags {
Name = "tf-test-dms-endpoint-%[1]s"
Update = "to-update"
Remove = "to-remove"
}
username = "tftest"
}
`, randId)
}
func dmsEndpointConfigUpdate(randId string) string {
return fmt.Sprintf(`
resource "aws_dms_endpoint" "dms_endpoint" {
database_name = "tf-test-dms-db-updated"
endpoint_id = "tf-test-dms-endpoint-%[1]s"
endpoint_type = "source"
engine_name = "aurora"
extra_connection_attributes = "extra"
password = "tftestupdate"
port = 3303
server_name = "tftestupdate"
ssl_mode = "none"
tags {
Name = "tf-test-dms-endpoint-%[1]s"
Update = "updated"
Add = "added"
}
username = "tftestupdate"
}
`, randId)
}

View File

@ -0,0 +1,422 @@
package aws
import (
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/private/waiter"
dms "github.com/aws/aws-sdk-go/service/databasemigrationservice"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsDmsReplicationInstance() *schema.Resource {
return &schema.Resource{
Create: resourceAwsDmsReplicationInstanceCreate,
Read: resourceAwsDmsReplicationInstanceRead,
Update: resourceAwsDmsReplicationInstanceUpdate,
Delete: resourceAwsDmsReplicationInstanceDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"allocated_storage": {
Type: schema.TypeInt,
Computed: true,
Optional: true,
ValidateFunc: validateIntegerInRange(5, 6144),
},
"apply_immediately": {
Type: schema.TypeBool,
Optional: true,
},
"auto_minor_version_upgrade": {
Type: schema.TypeBool,
Computed: true,
Optional: true,
},
"availability_zone": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"engine_version": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"kms_key_arn": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
ValidateFunc: validateArn,
},
"multi_az": {
Type: schema.TypeBool,
Computed: true,
Optional: true,
},
"preferred_maintenance_window": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ValidateFunc: validateOnceAWeekWindowFormat,
},
"publicly_accessible": {
Type: schema.TypeBool,
Computed: true,
Optional: true,
ForceNew: true,
},
"replication_instance_arn": {
Type: schema.TypeString,
Computed: true,
},
"replication_instance_class": {
Type: schema.TypeString,
Required: true,
// Valid Values: dms.t2.micro | dms.t2.small | dms.t2.medium | dms.t2.large | dms.c4.large |
// dms.c4.xlarge | dms.c4.2xlarge | dms.c4.4xlarge
},
"replication_instance_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateDmsReplicationInstanceId,
},
"replication_instance_private_ips": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Computed: true,
},
"replication_instance_public_ips": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Computed: true,
},
"replication_subnet_group_id": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"tags": {
Type: schema.TypeMap,
Optional: true,
},
"vpc_security_group_ids": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
Computed: true,
Optional: true,
},
},
}
}
func resourceAwsDmsReplicationInstanceCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn
request := &dms.CreateReplicationInstanceInput{
AutoMinorVersionUpgrade: aws.Bool(d.Get("auto_minor_version_upgrade").(bool)),
PubliclyAccessible: aws.Bool(d.Get("publicly_accessible").(bool)),
ReplicationInstanceClass: aws.String(d.Get("replication_instance_class").(string)),
ReplicationInstanceIdentifier: aws.String(d.Get("replication_instance_id").(string)),
Tags: dmsTagsFromMap(d.Get("tags").(map[string]interface{})),
}
// WARNING: GetOk returns the zero value for the type if the key is omitted in config. This means for optional
// keys that the zero value is valid we cannot know if the zero value was in the config and cannot allow the API
// to set the default value. See GitHub Issue #5694 https://github.com/hashicorp/terraform/issues/5694
if v, ok := d.GetOk("allocated_storage"); ok {
request.AllocatedStorage = aws.Int64(int64(v.(int)))
}
if v, ok := d.GetOk("engine_version"); ok {
request.EngineVersion = aws.String(v.(string))
}
if v, ok := d.GetOk("kms_key_arn"); ok {
request.KmsKeyId = aws.String(v.(string))
}
if v, ok := d.GetOk("preferred_maintenance_window"); ok {
request.PreferredMaintenanceWindow = aws.String(v.(string))
}
if v, ok := d.GetOk("replication_subnet_group_id"); ok {
request.ReplicationSubnetGroupIdentifier = aws.String(v.(string))
}
if v, ok := d.GetOk("vpc_security_group_ids"); ok {
request.VpcSecurityGroupIds = expandStringList(v.(*schema.Set).List())
}
az, azSet := d.GetOk("availability_zone")
if azSet {
request.AvailabilityZone = aws.String(az.(string))
}
if multiAz, ok := d.GetOk("multi_az"); ok {
request.MultiAZ = aws.Bool(multiAz.(bool))
if multiAz.(bool) && azSet {
return fmt.Errorf("Cannot set availability_zone if multi_az is set to true")
}
}
log.Println("[DEBUG] DMS create replication instance:", request)
_, err := conn.CreateReplicationInstance(request)
if err != nil {
return err
}
err = waitForInstanceCreated(conn, d.Get("replication_instance_id").(string), 30, 20)
if err != nil {
return err
}
d.SetId(d.Get("replication_instance_id").(string))
return resourceAwsDmsReplicationInstanceRead(d, meta)
}
func resourceAwsDmsReplicationInstanceRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn
response, err := conn.DescribeReplicationInstances(&dms.DescribeReplicationInstancesInput{
Filters: []*dms.Filter{
{
Name: aws.String("replication-instance-id"),
Values: []*string{aws.String(d.Id())}, // Must use d.Id() to work with import.
},
},
})
if err != nil {
if dmserr, ok := err.(awserr.Error); ok && dmserr.Code() == "ResourceNotFoundFault" {
d.SetId("")
return nil
}
return err
}
err = resourceAwsDmsReplicationInstanceSetState(d, response.ReplicationInstances[0])
if err != nil {
return err
}
tagsResp, err := conn.ListTagsForResource(&dms.ListTagsForResourceInput{
ResourceArn: aws.String(d.Get("replication_instance_arn").(string)),
})
if err != nil {
return err
}
d.Set("tags", dmsTagsToMap(tagsResp.TagList))
return nil
}
func resourceAwsDmsReplicationInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
request := &dms.ModifyReplicationInstanceInput{
ApplyImmediately: aws.Bool(d.Get("apply_immediately").(bool)),
ReplicationInstanceArn: aws.String(d.Get("replication_instance_arn").(string)),
}
hasChanges := false
if d.HasChange("auto_minor_version_upgrade") {
request.AutoMinorVersionUpgrade = aws.Bool(d.Get("auto_minor_version_upgrade").(bool))
hasChanges = true
}
if d.HasChange("allocated_storage") {
if v, ok := d.GetOk("allocated_storage"); ok {
request.AllocatedStorage = aws.Int64(int64(v.(int)))
hasChanges = true
}
}
if d.HasChange("engine_version") {
if v, ok := d.GetOk("engine_version"); ok {
request.ReplicationInstanceClass = aws.String(v.(string))
hasChanges = true
}
}
if d.HasChange("multi_az") {
if v, ok := d.GetOk("multi_az"); ok {
request.MultiAZ = aws.Bool(v.(bool))
hasChanges = true
}
}
if d.HasChange("preferred_maintenance_window") {
if v, ok := d.GetOk("preferred_maintenance_window"); ok {
request.PreferredMaintenanceWindow = aws.String(v.(string))
hasChanges = true
}
}
if d.HasChange("replication_instance_class") {
if v, ok := d.GetOk("replication_instance_class"); ok {
request.ReplicationInstanceClass = aws.String(v.(string))
hasChanges = true
}
}
if d.HasChange("vpc_security_group_ids") {
if v, ok := d.GetOk("vpc_security_group_ids"); ok {
request.VpcSecurityGroupIds = expandStringList(v.(*schema.Set).List())
hasChanges = true
}
}
if d.HasChange("tags") {
err := dmsSetTags(d.Get("replication_instance_arn").(string), d, meta)
if err != nil {
return err
}
}
if hasChanges {
conn := meta.(*AWSClient).dmsconn
_, err := conn.ModifyReplicationInstance(request)
if err != nil {
return err
}
return resourceAwsDmsReplicationInstanceRead(d, meta)
}
return nil
}
func resourceAwsDmsReplicationInstanceDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn
request := &dms.DeleteReplicationInstanceInput{
ReplicationInstanceArn: aws.String(d.Get("replication_instance_arn").(string)),
}
log.Printf("[DEBUG] DMS delete replication instance: %#v", request)
_, err := conn.DeleteReplicationInstance(request)
if err != nil {
return err
}
waitErr := waitForInstanceDeleted(conn, d.Get("replication_instance_id").(string), 30, 20)
if waitErr != nil {
return waitErr
}
return nil
}
func resourceAwsDmsReplicationInstanceSetState(d *schema.ResourceData, instance *dms.ReplicationInstance) error {
d.SetId(*instance.ReplicationInstanceIdentifier)
d.Set("replication_instance_id", instance.ReplicationInstanceIdentifier)
d.Set("allocated_storage", instance.AllocatedStorage)
d.Set("auto_minor_version_upgrade", instance.AutoMinorVersionUpgrade)
d.Set("availability_zone", instance.AvailabilityZone)
d.Set("engine_version", instance.EngineVersion)
d.Set("kms_key_arn", instance.KmsKeyId)
d.Set("multi_az", instance.MultiAZ)
d.Set("preferred_maintenance_window", instance.PreferredMaintenanceWindow)
d.Set("publicly_accessible", instance.PubliclyAccessible)
d.Set("replication_instance_arn", instance.ReplicationInstanceArn)
d.Set("replication_instance_class", instance.ReplicationInstanceClass)
d.Set("replication_subnet_group_id", instance.ReplicationSubnetGroup.ReplicationSubnetGroupIdentifier)
vpc_security_group_ids := []string{}
for _, sg := range instance.VpcSecurityGroups {
vpc_security_group_ids = append(vpc_security_group_ids, aws.StringValue(sg.VpcSecurityGroupId))
}
d.Set("vpc_security_group_ids", vpc_security_group_ids)
private_ip_addresses := []string{}
for _, ip := range instance.ReplicationInstancePrivateIpAddresses {
private_ip_addresses = append(private_ip_addresses, aws.StringValue(ip))
}
d.Set("replication_instance_private_ips", private_ip_addresses)
public_ip_addresses := []string{}
for _, ip := range instance.ReplicationInstancePublicIpAddresses {
public_ip_addresses = append(public_ip_addresses, aws.StringValue(ip))
}
d.Set("replication_instance_public_ips", public_ip_addresses)
return nil
}
func waitForInstanceCreated(client *dms.DatabaseMigrationService, id string, delay int, maxAttempts int) error {
input := &dms.DescribeReplicationInstancesInput{
Filters: []*dms.Filter{
{
Name: aws.String("replication-instance-id"),
Values: []*string{aws.String(id)},
},
},
}
config := waiter.Config{
Operation: "DescribeReplicationInstances",
Delay: delay,
MaxAttempts: maxAttempts,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "pathAll",
Argument: "ReplicationInstances[].ReplicationInstanceStatus",
Expected: "available",
},
},
}
w := waiter.Waiter{
Client: client,
Input: input,
Config: config,
}
return w.Wait()
}
func waitForInstanceDeleted(client *dms.DatabaseMigrationService, id string, delay int, maxAttempts int) error {
input := &dms.DescribeReplicationInstancesInput{
Filters: []*dms.Filter{
{
Name: aws.String("replication-instance-id"),
Values: []*string{aws.String(id)},
},
},
}
config := waiter.Config{
Operation: "DescribeReplicationInstances",
Delay: delay,
MaxAttempts: maxAttempts,
Acceptors: []waiter.WaitAcceptor{
{
State: "success",
Matcher: "path",
Argument: "length(ReplicationInstances[]) > `0`",
Expected: false,
},
},
}
w := waiter.Waiter{
Client: client,
Input: input,
Config: config,
}
return w.Wait()
}

View File

@ -0,0 +1,232 @@
package aws
import (
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
dms "github.com/aws/aws-sdk-go/service/databasemigrationservice"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAwsDmsReplicationInstanceBasic(t *testing.T) {
resourceName := "aws_dms_replication_instance.dms_replication_instance"
randId := acctest.RandString(8)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: dmsReplicationInstanceDestroy,
Steps: []resource.TestStep{
{
Config: dmsReplicationInstanceConfig(randId),
Check: resource.ComposeTestCheckFunc(
checkDmsReplicationInstanceExists(resourceName),
resource.TestCheckResourceAttrSet(resourceName, "replication_instance_arn"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
{
Config: dmsReplicationInstanceConfigUpdate(randId),
Check: resource.ComposeTestCheckFunc(
checkDmsReplicationInstanceExists(resourceName),
resource.TestCheckResourceAttrSet(resourceName, "apply_immediately"),
resource.TestCheckResourceAttr(resourceName, "auto_minor_version_upgrade", "false"),
resource.TestCheckResourceAttr(resourceName, "preferred_maintenance_window", "mon:00:30-mon:02:30"),
),
},
},
})
}
func checkDmsReplicationInstanceExists(n string) resource.TestCheckFunc {
providers := []*schema.Provider{testAccProvider}
return checkDmsReplicationInstanceExistsWithProviders(n, &providers)
}
func checkDmsReplicationInstanceExistsWithProviders(n string, providers *[]*schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
for _, provider := range *providers {
// Ignore if Meta is empty, this can happen for validation providers
if provider.Meta() == nil {
continue
}
conn := provider.Meta().(*AWSClient).dmsconn
_, err := conn.DescribeReplicationInstances(&dms.DescribeReplicationInstancesInput{
Filters: []*dms.Filter{
{
Name: aws.String("replication-instance-id"),
Values: []*string{aws.String(rs.Primary.ID)},
},
},
})
if err != nil {
return fmt.Errorf("DMS replication instance error: %v", err)
}
return nil
}
return fmt.Errorf("DMS replication instance not found")
}
}
func dmsReplicationInstanceDestroy(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_dms_replication_instance" {
continue
}
err := checkDmsReplicationInstanceExists(rs.Primary.ID)
if err == nil {
return fmt.Errorf("Found replication instance that was not destroyed: %s", rs.Primary.ID)
}
}
return nil
}
func dmsReplicationInstanceConfig(randId string) string {
return fmt.Sprintf(`
resource "aws_iam_role" "dms_iam_role" {
name = "dms-vpc-role"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"dms.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
resource "aws_iam_role_policy_attachment" "dms_iam_role_policy" {
role = "${aws_iam_role.dms_iam_role.name}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonDMSVPCManagementRole"
}
resource "aws_vpc" "dms_vpc" {
cidr_block = "10.1.0.0/16"
tags {
Name = "tf-test-dms-vpc-%[1]s"
}
depends_on = ["aws_iam_role_policy_attachment.dms_iam_role_policy"]
}
resource "aws_subnet" "dms_subnet_1" {
cidr_block = "10.1.1.0/24"
availability_zone = "us-west-2a"
vpc_id = "${aws_vpc.dms_vpc.id}"
tags {
Name = "tf-test-dms-subnet-%[1]s"
}
depends_on = ["aws_vpc.dms_vpc"]
}
resource "aws_subnet" "dms_subnet_2" {
cidr_block = "10.1.2.0/24"
availability_zone = "us-west-2b"
vpc_id = "${aws_vpc.dms_vpc.id}"
tags {
Name = "tf-test-dms-subnet-%[1]s"
}
depends_on = ["aws_vpc.dms_vpc"]
}
resource "aws_dms_replication_subnet_group" "dms_replication_subnet_group" {
replication_subnet_group_id = "tf-test-dms-replication-subnet-group-%[1]s"
replication_subnet_group_description = "terraform test for replication subnet group"
subnet_ids = ["${aws_subnet.dms_subnet_1.id}", "${aws_subnet.dms_subnet_2.id}"]
depends_on = ["aws_iam_role_policy_attachment.dms_iam_role_policy"]
}
resource "aws_dms_replication_instance" "dms_replication_instance" {
allocated_storage = 5
auto_minor_version_upgrade = true
replication_instance_class = "dms.t2.micro"
replication_instance_id = "tf-test-dms-replication-instance-%[1]s"
preferred_maintenance_window = "sun:00:30-sun:02:30"
publicly_accessible = false
replication_subnet_group_id = "${aws_dms_replication_subnet_group.dms_replication_subnet_group.replication_subnet_group_id}"
tags {
Name = "tf-test-dms-replication-instance-%[1]s"
Update = "to-update"
Remove = "to-remove"
}
}
`, randId)
}
func dmsReplicationInstanceConfigUpdate(randId string) string {
return fmt.Sprintf(`
resource "aws_iam_role" "dms_iam_role" {
name = "dms-vpc-role"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"dms.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
resource "aws_iam_role_policy_attachment" "dms_iam_role_policy" {
role = "${aws_iam_role.dms_iam_role.name}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonDMSVPCManagementRole"
}
resource "aws_vpc" "dms_vpc" {
cidr_block = "10.1.0.0/16"
tags {
Name = "tf-test-dms-vpc-%[1]s"
}
depends_on = ["aws_iam_role_policy_attachment.dms_iam_role_policy"]
}
resource "aws_subnet" "dms_subnet_1" {
cidr_block = "10.1.1.0/24"
availability_zone = "us-west-2a"
vpc_id = "${aws_vpc.dms_vpc.id}"
tags {
Name = "tf-test-dms-subnet-%[1]s"
}
depends_on = ["aws_vpc.dms_vpc"]
}
resource "aws_subnet" "dms_subnet_2" {
cidr_block = "10.1.2.0/24"
availability_zone = "us-west-2b"
vpc_id = "${aws_vpc.dms_vpc.id}"
tags {
Name = "tf-test-dms-subnet-%[1]s"
}
depends_on = ["aws_vpc.dms_vpc"]
}
resource "aws_dms_replication_subnet_group" "dms_replication_subnet_group" {
replication_subnet_group_id = "tf-test-dms-replication-subnet-group-%[1]s"
replication_subnet_group_description = "terraform test for replication subnet group"
subnet_ids = ["${aws_subnet.dms_subnet_1.id}", "${aws_subnet.dms_subnet_2.id}"]
depends_on = ["aws_iam_role_policy_attachment.dms_iam_role_policy"]
}
resource "aws_dms_replication_instance" "dms_replication_instance" {
allocated_storage = 5
apply_immediately = true
auto_minor_version_upgrade = false
replication_instance_class = "dms.t2.micro"
replication_instance_id = "tf-test-dms-replication-instance-%[1]s"
preferred_maintenance_window = "mon:00:30-mon:02:30"
publicly_accessible = false
replication_subnet_group_id = "${aws_dms_replication_subnet_group.dms_replication_subnet_group.replication_subnet_group_id}"
tags {
Name = "tf-test-dms-replication-instance-%[1]s"
Update = "updated"
Add = "added"
}
}
`, randId)
}

View File

@ -0,0 +1,179 @@
package aws
import (
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws"
dms "github.com/aws/aws-sdk-go/service/databasemigrationservice"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsDmsReplicationSubnetGroup() *schema.Resource {
return &schema.Resource{
Create: resourceAwsDmsReplicationSubnetGroupCreate,
Read: resourceAwsDmsReplicationSubnetGroupRead,
Update: resourceAwsDmsReplicationSubnetGroupUpdate,
Delete: resourceAwsDmsReplicationSubnetGroupDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"replication_subnet_group_arn": {
Type: schema.TypeString,
Computed: true,
},
"replication_subnet_group_description": {
Type: schema.TypeString,
Required: true,
},
"replication_subnet_group_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateDmsReplicationSubnetGroupId,
},
"subnet_ids": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
Required: true,
},
"tags": {
Type: schema.TypeMap,
Optional: true,
},
"vpc_id": {
Type: schema.TypeString,
Computed: true,
},
},
}
}
func resourceAwsDmsReplicationSubnetGroupCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn
request := &dms.CreateReplicationSubnetGroupInput{
ReplicationSubnetGroupIdentifier: aws.String(d.Get("replication_subnet_group_id").(string)),
ReplicationSubnetGroupDescription: aws.String(d.Get("replication_subnet_group_description").(string)),
SubnetIds: expandStringList(d.Get("subnet_ids").(*schema.Set).List()),
Tags: dmsTagsFromMap(d.Get("tags").(map[string]interface{})),
}
log.Println("[DEBUG] DMS create replication subnet group:", request)
_, err := conn.CreateReplicationSubnetGroup(request)
if err != nil {
return err
}
d.SetId(d.Get("replication_subnet_group_id").(string))
return resourceAwsDmsReplicationSubnetGroupRead(d, meta)
}
func resourceAwsDmsReplicationSubnetGroupRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn
response, err := conn.DescribeReplicationSubnetGroups(&dms.DescribeReplicationSubnetGroupsInput{
Filters: []*dms.Filter{
{
Name: aws.String("replication-subnet-group-id"),
Values: []*string{aws.String(d.Id())}, // Must use d.Id() to work with import.
},
},
})
if err != nil {
return err
}
if len(response.ReplicationSubnetGroups) == 0 {
d.SetId("")
return nil
}
// The AWS API for DMS subnet groups does not return the ARN which is required to
// retrieve tags. This ARN can be built.
d.Set("replication_subnet_group_arn", fmt.Sprintf("arn:aws:dms:%s:%s:subgrp:%s",
meta.(*AWSClient).region, meta.(*AWSClient).accountid, d.Id()))
err = resourceAwsDmsReplicationSubnetGroupSetState(d, response.ReplicationSubnetGroups[0])
if err != nil {
return err
}
tagsResp, err := conn.ListTagsForResource(&dms.ListTagsForResourceInput{
ResourceArn: aws.String(d.Get("replication_subnet_group_arn").(string)),
})
if err != nil {
return err
}
d.Set("tags", dmsTagsToMap(tagsResp.TagList))
return nil
}
func resourceAwsDmsReplicationSubnetGroupUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn
// Updates to subnet groups are only valid when sending SubnetIds even if there are no
// changes to SubnetIds.
request := &dms.ModifyReplicationSubnetGroupInput{
ReplicationSubnetGroupIdentifier: aws.String(d.Get("replication_subnet_group_id").(string)),
SubnetIds: expandStringList(d.Get("subnet_ids").(*schema.Set).List()),
}
if d.HasChange("replication_subnet_group_description") {
request.ReplicationSubnetGroupDescription = aws.String(d.Get("replication_subnet_group_description").(string))
}
if d.HasChange("tags") {
err := dmsSetTags(d.Get("replication_subnet_group_arn").(string), d, meta)
if err != nil {
return err
}
}
log.Println("[DEBUG] DMS update replication subnet group:", request)
_, err := conn.ModifyReplicationSubnetGroup(request)
if err != nil {
return err
}
return resourceAwsDmsReplicationSubnetGroupRead(d, meta)
}
func resourceAwsDmsReplicationSubnetGroupDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn
request := &dms.DeleteReplicationSubnetGroupInput{
ReplicationSubnetGroupIdentifier: aws.String(d.Get("replication_subnet_group_id").(string)),
}
log.Printf("[DEBUG] DMS delete replication subnet group: %#v", request)
_, err := conn.DeleteReplicationSubnetGroup(request)
if err != nil {
return err
}
return nil
}
func resourceAwsDmsReplicationSubnetGroupSetState(d *schema.ResourceData, group *dms.ReplicationSubnetGroup) error {
d.SetId(*group.ReplicationSubnetGroupIdentifier)
subnet_ids := []string{}
for _, subnet := range group.Subnets {
subnet_ids = append(subnet_ids, aws.StringValue(subnet.SubnetIdentifier))
}
d.Set("replication_subnet_group_description", group.ReplicationSubnetGroupDescription)
d.Set("replication_subnet_group_id", group.ReplicationSubnetGroupIdentifier)
d.Set("subnet_ids", subnet_ids)
d.Set("vpc_id", group.VpcId)
return nil
}

View File

@ -0,0 +1,226 @@
package aws
import (
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
dms "github.com/aws/aws-sdk-go/service/databasemigrationservice"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAwsDmsReplicationSubnetGroupBasic(t *testing.T) {
resourceName := "aws_dms_replication_subnet_group.dms_replication_subnet_group"
randId := acctest.RandString(8)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: dmsReplicationSubnetGroupDestroy,
Steps: []resource.TestStep{
{
Config: dmsReplicationSubnetGroupConfig(randId),
Check: resource.ComposeTestCheckFunc(
checkDmsReplicationSubnetGroupExists(resourceName),
resource.TestCheckResourceAttrSet(resourceName, "vpc_id"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
{
Config: dmsReplicationSubnetGroupConfigUpdate(randId),
Check: resource.ComposeTestCheckFunc(
checkDmsReplicationSubnetGroupExists(resourceName),
),
},
},
})
}
func checkDmsReplicationSubnetGroupExists(n string) resource.TestCheckFunc {
providers := []*schema.Provider{testAccProvider}
return checkDmsReplicationSubnetGroupExistsWithProviders(n, &providers)
}
func checkDmsReplicationSubnetGroupExistsWithProviders(n string, providers *[]*schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
for _, provider := range *providers {
// Ignore if Meta is empty, this can happen for validation providers
if provider.Meta() == nil {
continue
}
conn := provider.Meta().(*AWSClient).dmsconn
_, err := conn.DescribeReplicationSubnetGroups(&dms.DescribeReplicationSubnetGroupsInput{
Filters: []*dms.Filter{
{
Name: aws.String("replication-subnet-group-id"),
Values: []*string{aws.String(rs.Primary.ID)},
},
},
})
if err != nil {
return fmt.Errorf("DMS replication subnet group error: %v", err)
}
return nil
}
return fmt.Errorf("DMS replication subnet group not found")
}
}
func dmsReplicationSubnetGroupDestroy(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_dms_replication_subnet_group" {
continue
}
err := checkDmsReplicationSubnetGroupExists(rs.Primary.ID)
if err == nil {
return fmt.Errorf("Found replication subnet group that was not destroyed: %s", rs.Primary.ID)
}
}
return nil
}
func dmsReplicationSubnetGroupConfig(randId string) string {
return fmt.Sprintf(`
resource "aws_iam_role" "dms_iam_role" {
name = "dms-vpc-role"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"dms.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
resource "aws_iam_role_policy_attachment" "dms_iam_role_policy" {
role = "${aws_iam_role.dms_iam_role.name}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonDMSVPCManagementRole"
}
resource "aws_vpc" "dms_vpc" {
cidr_block = "10.1.0.0/16"
tags {
Name = "tf-test-dms-vpc-%[1]s"
}
depends_on = ["aws_iam_role_policy_attachment.dms_iam_role_policy"]
}
resource "aws_subnet" "dms_subnet_1" {
cidr_block = "10.1.1.0/24"
availability_zone = "us-west-2a"
vpc_id = "${aws_vpc.dms_vpc.id}"
tags {
Name = "tf-test-dms-subnet-%[1]s"
}
depends_on = ["aws_vpc.dms_vpc"]
}
resource "aws_subnet" "dms_subnet_2" {
cidr_block = "10.1.2.0/24"
availability_zone = "us-west-2b"
vpc_id = "${aws_vpc.dms_vpc.id}"
tags {
Name = "tf-test-dms-subnet-%[1]s"
}
depends_on = ["aws_vpc.dms_vpc"]
}
resource "aws_subnet" "dms_subnet_3" {
cidr_block = "10.1.3.0/24"
availability_zone = "us-west-2b"
vpc_id = "${aws_vpc.dms_vpc.id}"
tags {
Name = "tf-test-dms-subnet-%[1]s"
}
depends_on = ["aws_vpc.dms_vpc"]
}
resource "aws_dms_replication_subnet_group" "dms_replication_subnet_group" {
replication_subnet_group_id = "tf-test-dms-replication-subnet-group-%[1]s"
replication_subnet_group_description = "terraform test for replication subnet group"
subnet_ids = ["${aws_subnet.dms_subnet_1.id}", "${aws_subnet.dms_subnet_2.id}"]
tags {
Name = "tf-test-dms-replication-subnet-group-%[1]s"
Update = "to-update"
Remove = "to-remove"
}
}
`, randId)
}
func dmsReplicationSubnetGroupConfigUpdate(randId string) string {
return fmt.Sprintf(`
resource "aws_iam_role" "dms_iam_role" {
name = "dms-vpc-role"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"dms.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
resource "aws_iam_role_policy_attachment" "dms_iam_role_policy" {
role = "${aws_iam_role.dms_iam_role.name}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonDMSVPCManagementRole"
}
resource "aws_vpc" "dms_vpc" {
cidr_block = "10.1.0.0/16"
tags {
Name = "tf-test-dms-vpc-%[1]s"
}
depends_on = ["aws_iam_role_policy_attachment.dms_iam_role_policy"]
}
resource "aws_subnet" "dms_subnet_1" {
cidr_block = "10.1.1.0/24"
availability_zone = "us-west-2a"
vpc_id = "${aws_vpc.dms_vpc.id}"
tags {
Name = "tf-test-dms-subnet-%[1]s"
}
depends_on = ["aws_vpc.dms_vpc"]
}
resource "aws_subnet" "dms_subnet_2" {
cidr_block = "10.1.2.0/24"
availability_zone = "us-west-2b"
vpc_id = "${aws_vpc.dms_vpc.id}"
tags {
Name = "tf-test-dms-subnet-%[1]s"
}
depends_on = ["aws_vpc.dms_vpc"]
}
resource "aws_subnet" "dms_subnet_3" {
cidr_block = "10.1.3.0/24"
availability_zone = "us-west-2b"
vpc_id = "${aws_vpc.dms_vpc.id}"
tags {
Name = "tf-test-dms-subnet-%[1]s"
}
depends_on = ["aws_vpc.dms_vpc"]
}
resource "aws_dms_replication_subnet_group" "dms_replication_subnet_group" {
replication_subnet_group_id = "tf-test-dms-replication-subnet-group-%[1]s"
replication_subnet_group_description = "terraform test for replication subnet group"
subnet_ids = ["${aws_subnet.dms_subnet_1.id}", "${aws_subnet.dms_subnet_3.id}"]
tags {
Name = "tf-test-dms-replication-subnet-group-%[1]s"
Update = "updated"
Add = "added"
}
}
`, randId)
}

View File

@ -0,0 +1,377 @@
package aws
import (
"fmt"
"log"
"strconv"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/private/waiter"
dms "github.com/aws/aws-sdk-go/service/databasemigrationservice"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
)
func resourceAwsDmsReplicationTask() *schema.Resource {
return &schema.Resource{
Create: resourceAwsDmsReplicationTaskCreate,
Read: resourceAwsDmsReplicationTaskRead,
Update: resourceAwsDmsReplicationTaskUpdate,
Delete: resourceAwsDmsReplicationTaskDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"cdc_start_time": {
Type: schema.TypeInt,
Optional: true,
// Requires a Unix timestamp in seconds. Example 1484346880
},
"migration_type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
"full-load",
"cdc",
"full-load-and-cdc",
}, false),
},
"replication_instance_arn": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateArn,
},
"replication_task_arn": {
Type: schema.TypeString,
Computed: true,
},
"replication_task_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateDmsReplicationTaskId,
},
"replication_task_settings": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateJsonString,
},
"source_endpoint_arn": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateArn,
},
"table_mappings": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validateJsonString,
},
"tags": {
Type: schema.TypeMap,
Optional: true,
},
"target_endpoint_arn": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateArn,
},
},
}
}
func resourceAwsDmsReplicationTaskCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn
request := &dms.CreateReplicationTaskInput{
MigrationType: aws.String(d.Get("migration_type").(string)),
ReplicationInstanceArn: aws.String(d.Get("replication_instance_arn").(string)),
ReplicationTaskIdentifier: aws.String(d.Get("replication_task_id").(string)),
SourceEndpointArn: aws.String(d.Get("source_endpoint_arn").(string)),
TableMappings: aws.String(d.Get("table_mappings").(string)),
Tags: dmsTagsFromMap(d.Get("tags").(map[string]interface{})),
TargetEndpointArn: aws.String(d.Get("target_endpoint_arn").(string)),
}
if v, ok := d.GetOk("cdc_start_time"); ok {
seconds, err := strconv.ParseInt(v.(string), 10, 64)
if err != nil {
return fmt.Errorf("[ERROR] DMS create replication task. Invalid CDC Unix timestamp: %s", err)
}
request.CdcStartTime = aws.Time(time.Unix(seconds, 0))
}
if v, ok := d.GetOk("replication_task_settings"); ok {
request.ReplicationTaskSettings = aws.String(v.(string))
}
log.Println("[DEBUG] DMS create replication task:", request)
_, err := conn.CreateReplicationTask(request)
if err != nil {
return err
}
taskId := d.Get("replication_task_id").(string)
err = waitForTaskCreated(conn, taskId, 30, 10)
if err != nil {
return err
}
d.SetId(taskId)
return resourceAwsDmsReplicationTaskRead(d, meta)
}
func resourceAwsDmsReplicationTaskRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn
response, err := conn.DescribeReplicationTasks(&dms.DescribeReplicationTasksInput{
Filters: []*dms.Filter{
{
Name: aws.String("replication-task-id"),
Values: []*string{aws.String(d.Id())}, // Must use d.Id() to work with import.
},
},
})
if err != nil {
if dmserr, ok := err.(awserr.Error); ok && dmserr.Code() == "ResourceNotFoundFault" {
d.SetId("")
return nil
}
return err
}
err = resourceAwsDmsReplicationTaskSetState(d, response.ReplicationTasks[0])
if err != nil {
return err
}
tagsResp, err := conn.ListTagsForResource(&dms.ListTagsForResourceInput{
ResourceArn: aws.String(d.Get("replication_task_arn").(string)),
})
if err != nil {
return err
}
d.Set("tags", dmsTagsToMap(tagsResp.TagList))
return nil
}
func resourceAwsDmsReplicationTaskUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn
request := &dms.ModifyReplicationTaskInput{
ReplicationTaskArn: aws.String(d.Get("replication_task_arn").(string)),
}
hasChanges := false
if d.HasChange("cdc_start_time") {
seconds, err := strconv.ParseInt(d.Get("cdc_start_time").(string), 10, 64)
if err != nil {
return fmt.Errorf("[ERROR] DMS update replication task. Invalid CRC Unix timestamp: %s", err)
}
request.CdcStartTime = aws.Time(time.Unix(seconds, 0))
hasChanges = true
}
if d.HasChange("migration_type") {
request.MigrationType = aws.String(d.Get("migration_type").(string))
hasChanges = true
}
if d.HasChange("replication_task_settings") {
request.ReplicationTaskSettings = aws.String(d.Get("replication_task_settings").(string))
hasChanges = true
}
if d.HasChange("table_mappings") {
request.TableMappings = aws.String(d.Get("table_mappings").(string))
hasChanges = true
}
if d.HasChange("tags") {
err := dmsSetTags(d.Get("replication_task_arn").(string), d, meta)
if err != nil {
return err
}
}
if hasChanges {
log.Println("[DEBUG] DMS update replication task:", request)
_, err := conn.ModifyReplicationTask(request)
if err != nil {
return err
}
err = waitForTaskUpdated(conn, d.Get("replication_task_id").(string), 30, 10)
if err != nil {
return err
}
return resourceAwsDmsReplicationTaskRead(d, meta)
}
return nil
}
func resourceAwsDmsReplicationTaskDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn
request := &dms.DeleteReplicationTaskInput{
ReplicationTaskArn: aws.String(d.Get("replication_task_arn").(string)),
}
log.Printf("[DEBUG] DMS delete replication task: %#v", request)
_, err := conn.DeleteReplicationTask(request)
if err != nil {
return err
}
waitErr := waitForTaskDeleted(conn, d.Get("replication_task_id").(string), 30, 10)
if waitErr != nil {
return waitErr
}
return nil
}
func resourceAwsDmsReplicationTaskSetState(d *schema.ResourceData, task *dms.ReplicationTask) error {
d.SetId(*task.ReplicationTaskIdentifier)
d.Set("migration_type", task.MigrationType)
d.Set("replication_instance_arn", task.ReplicationInstanceArn)
d.Set("replication_task_arn", task.ReplicationTaskArn)
d.Set("replication_task_id", task.ReplicationTaskIdentifier)
d.Set("replication_task_settings", task.ReplicationTaskSettings)
d.Set("source_endpoint_arn", task.SourceEndpointArn)
d.Set("table_mappings", task.TableMappings)
d.Set("target_endpoint_arn", task.TargetEndpointArn)
return nil
}
func waitForTaskCreated(client *dms.DatabaseMigrationService, id string, delay int, maxAttempts int) error {
input := &dms.DescribeReplicationTasksInput{
Filters: []*dms.Filter{
{
Name: aws.String("replication-task-id"),
Values: []*string{aws.String(id)},
},
},
}
config := waiter.Config{
Operation: "DescribeReplicationTasks",
Delay: delay,
MaxAttempts: maxAttempts,
Acceptors: []waiter.WaitAcceptor{
{
State: "retry",
Matcher: "pathAll",
Argument: "ReplicationTasks[].Status",
Expected: "creating",
},
{
State: "success",
Matcher: "pathAll",
Argument: "ReplicationTasks[].Status",
Expected: "ready",
},
},
}
w := waiter.Waiter{
Client: client,
Input: input,
Config: config,
}
return w.Wait()
}
func waitForTaskUpdated(client *dms.DatabaseMigrationService, id string, delay int, maxAttempts int) error {
input := &dms.DescribeReplicationTasksInput{
Filters: []*dms.Filter{
{
Name: aws.String("replication-task-id"),
Values: []*string{aws.String(id)},
},
},
}
config := waiter.Config{
Operation: "DescribeReplicationTasks",
Delay: delay,
MaxAttempts: maxAttempts,
Acceptors: []waiter.WaitAcceptor{
{
State: "retry",
Matcher: "pathAll",
Argument: "ReplicationTasks[].Status",
Expected: "modifying",
},
{
State: "success",
Matcher: "pathAll",
Argument: "ReplicationTasks[].Status",
Expected: "ready",
},
},
}
w := waiter.Waiter{
Client: client,
Input: input,
Config: config,
}
return w.Wait()
}
func waitForTaskDeleted(client *dms.DatabaseMigrationService, id string, delay int, maxAttempts int) error {
input := &dms.DescribeReplicationTasksInput{
Filters: []*dms.Filter{
{
Name: aws.String("replication-task-id"),
Values: []*string{aws.String(id)},
},
},
}
config := waiter.Config{
Operation: "DescribeReplicationTasks",
Delay: delay,
MaxAttempts: maxAttempts,
Acceptors: []waiter.WaitAcceptor{
{
State: "retry",
Matcher: "pathAll",
Argument: "ReplicationTasks[].Status",
Expected: "deleting",
},
{
State: "success",
Matcher: "path",
Argument: "length(ReplicationTasks[]) > `0`",
Expected: false,
},
},
}
w := waiter.Waiter{
Client: client,
Input: input,
Config: config,
}
return w.Wait()
}

View File

@ -0,0 +1,296 @@
package aws
import (
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
dms "github.com/aws/aws-sdk-go/service/databasemigrationservice"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAwsDmsReplicationTaskBasic(t *testing.T) {
resourceName := "aws_dms_replication_task.dms_replication_task"
randId := acctest.RandString(8)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: dmsReplicationTaskDestroy,
Steps: []resource.TestStep{
{
Config: dmsReplicationTaskConfig(randId),
Check: resource.ComposeTestCheckFunc(
checkDmsReplicationTaskExists(resourceName),
resource.TestCheckResourceAttrSet(resourceName, "replication_task_arn"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
{
Config: dmsReplicationTaskConfigUpdate(randId),
Check: resource.ComposeTestCheckFunc(
checkDmsReplicationTaskExists(resourceName),
),
},
},
})
}
func checkDmsReplicationTaskExists(n string) resource.TestCheckFunc {
providers := []*schema.Provider{testAccProvider}
return checkDmsReplicationTaskExistsWithProviders(n, &providers)
}
func checkDmsReplicationTaskExistsWithProviders(n string, providers *[]*schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
for _, provider := range *providers {
// Ignore if Meta is empty, this can happen for validation providers
if provider.Meta() == nil {
continue
}
conn := provider.Meta().(*AWSClient).dmsconn
_, err := conn.DescribeReplicationTasks(&dms.DescribeReplicationTasksInput{
Filters: []*dms.Filter{
{
Name: aws.String("replication-task-id"),
Values: []*string{aws.String(rs.Primary.ID)},
},
},
})
if err != nil {
return fmt.Errorf("DMS replication subnet group error: %v", err)
}
return nil
}
return fmt.Errorf("DMS replication subnet group not found")
}
}
func dmsReplicationTaskDestroy(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_dms_replication_task" {
continue
}
err := checkDmsReplicationTaskExists(rs.Primary.ID)
if err == nil {
return fmt.Errorf("Found replication subnet group that was not destroyed: %s", rs.Primary.ID)
}
}
return nil
}
func dmsReplicationTaskConfig(randId string) string {
return fmt.Sprintf(`
resource "aws_iam_role" "dms_iam_role" {
name = "dms-vpc-role"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"dms.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
resource "aws_iam_role_policy_attachment" "dms_iam_role_policy" {
role = "${aws_iam_role.dms_iam_role.name}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonDMSVPCManagementRole"
}
resource "aws_vpc" "dms_vpc" {
cidr_block = "10.1.0.0/16"
tags {
Name = "tf-test-dms-vpc-%[1]s"
}
depends_on = ["aws_iam_role_policy_attachment.dms_iam_role_policy"]
}
resource "aws_subnet" "dms_subnet_1" {
cidr_block = "10.1.1.0/24"
availability_zone = "us-west-2a"
vpc_id = "${aws_vpc.dms_vpc.id}"
tags {
Name = "tf-test-dms-subnet-%[1]s"
}
depends_on = ["aws_vpc.dms_vpc"]
}
resource "aws_subnet" "dms_subnet_2" {
cidr_block = "10.1.2.0/24"
availability_zone = "us-west-2b"
vpc_id = "${aws_vpc.dms_vpc.id}"
tags {
Name = "tf-test-dms-subnet-%[1]s"
}
depends_on = ["aws_vpc.dms_vpc"]
}
resource "aws_dms_endpoint" "dms_endpoint_source" {
database_name = "tf-test-dms-db"
endpoint_id = "tf-test-dms-endpoint-source-%[1]s"
endpoint_type = "source"
engine_name = "aurora"
server_name = "tf-test-cluster.cluster-xxxxxxx.us-west-2.rds.amazonaws.com"
port = 3306
username = "tftest"
password = "tftest"
depends_on = ["aws_iam_role_policy_attachment.dms_iam_role_policy"]
}
resource "aws_dms_endpoint" "dms_endpoint_target" {
database_name = "tf-test-dms-db"
endpoint_id = "tf-test-dms-endpoint-target-%[1]s"
endpoint_type = "target"
engine_name = "aurora"
server_name = "tf-test-cluster.cluster-xxxxxxx.us-west-2.rds.amazonaws.com"
port = 3306
username = "tftest"
password = "tftest"
depends_on = ["aws_iam_role_policy_attachment.dms_iam_role_policy"]
}
resource "aws_dms_replication_subnet_group" "dms_replication_subnet_group" {
replication_subnet_group_id = "tf-test-dms-replication-subnet-group-%[1]s"
replication_subnet_group_description = "terraform test for replication subnet group"
subnet_ids = ["${aws_subnet.dms_subnet_1.id}", "${aws_subnet.dms_subnet_2.id}"]
depends_on = ["aws_iam_role_policy_attachment.dms_iam_role_policy"]
}
resource "aws_dms_replication_instance" "dms_replication_instance" {
allocated_storage = 5
auto_minor_version_upgrade = true
replication_instance_class = "dms.t2.micro"
replication_instance_id = "tf-test-dms-replication-instance-%[1]s"
preferred_maintenance_window = "sun:00:30-sun:02:30"
publicly_accessible = false
replication_subnet_group_id = "${aws_dms_replication_subnet_group.dms_replication_subnet_group.replication_subnet_group_id}"
}
resource "aws_dms_replication_task" "dms_replication_task" {
migration_type = "full-load"
replication_instance_arn = "${aws_dms_replication_instance.dms_replication_instance.replication_instance_arn}"
replication_task_id = "tf-test-dms-replication-task-%[1]s"
replication_task_settings = "{\"TargetMetadata\":{\"TargetSchema\":\"\",\"SupportLobs\":true,\"FullLobMode\":false,\"LobChunkSize\":0,\"LimitedSizeLobMode\":true,\"LobMaxSize\":32,\"LoadMaxFileSize\":0,\"ParallelLoadThreads\":0,\"BatchApplyEnabled\":false},\"FullLoadSettings\":{\"FullLoadEnabled\":true,\"ApplyChangesEnabled\":false,\"TargetTablePrepMode\":\"DROP_AND_CREATE\",\"CreatePkAfterFullLoad\":false,\"StopTaskCachedChangesApplied\":false,\"StopTaskCachedChangesNotApplied\":false,\"ResumeEnabled\":false,\"ResumeMinTableSize\":100000,\"ResumeOnlyClusteredPKTables\":true,\"MaxFullLoadSubTasks\":8,\"TransactionConsistencyTimeout\":600,\"CommitRate\":10000},\"Logging\":{\"EnableLogging\":false,\"LogComponents\":[{\"Id\":\"SOURCE_UNLOAD\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"},{\"Id\":\"TARGET_LOAD\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"},{\"Id\":\"SOURCE_CAPTURE\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"},{\"Id\":\"TARGET_APPLY\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"},{\"Id\":\"TASK_MANAGER\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"}],\"CloudWatchLogGroup\":null,\"CloudWatchLogStream\":null},\"ControlTablesSettings\":{\"historyTimeslotInMinutes\":5,\"ControlSchema\":\"\",\"HistoryTimeslotInMinutes\":5,\"HistoryTableEnabled\":false,\"SuspendedTablesTableEnabled\":false,\"StatusTableEnabled\":false},\"StreamBufferSettings\":{\"StreamBufferCount\":3,\"StreamBufferSizeInMB\":8,\"CtrlStreamBufferSizeInMB\":5},\"ChangeProcessingDdlHandlingPolicy\":{\"HandleSourceTableDropped\":true,\"HandleSourceTableTruncated\":true,\"HandleSourceTableAltered\":true},\"ErrorBehavior\":{\"DataErrorPolicy\":\"LOG_ERROR\",\"DataTruncationErrorPolicy\":\"LOG_ERROR\",\"DataErrorEscalationPolicy\":\"SUSPEND_TABLE\",\"DataErrorEscalationCount\":0,\"TableErrorPolicy\":\"SUSPEND_TABLE\",\"TableErrorEscalationPolicy\":\"STOP_TASK\",\"TableErrorEscalationCount\":0,\"RecoverableErrorCount\":-1,\"RecoverableErrorInterval\":5,\"RecoverableErrorThrottling\":true,\"RecoverableErrorThrottlingMax\":1800,\"ApplyErrorDeletePolicy\":\"IGNORE_RECORD\",\"ApplyErrorInsertPolicy\":\"LOG_ERROR\",\"ApplyErrorUpdatePolicy\":\"LOG_ERROR\",\"ApplyErrorEscalationPolicy\":\"LOG_ERROR\",\"ApplyErrorEscalationCount\":0,\"FullLoadIgnoreConflicts\":true},\"ChangeProcessingTuning\":{\"BatchApplyPreserveTransaction\":true,\"BatchApplyTimeoutMin\":1,\"BatchApplyTimeoutMax\":30,\"BatchApplyMemoryLimit\":500,\"BatchSplitSize\":0,\"MinTransactionSize\":1000,\"CommitTimeout\":1,\"MemoryLimitTotal\":1024,\"MemoryKeepTime\":60,\"StatementCacheSize\":50}}"
source_endpoint_arn = "${aws_dms_endpoint.dms_endpoint_source.endpoint_arn}"
table_mappings = "{\"rules\":[{\"rule-type\":\"selection\",\"rule-id\":\"1\",\"rule-name\":\"1\",\"object-locator\":{\"schema-name\":\"%%\",\"table-name\":\"%%\"},\"rule-action\":\"include\"}]}"
tags {
Name = "tf-test-dms-replication-task-%[1]s"
Update = "to-update"
Remove = "to-remove"
}
target_endpoint_arn = "${aws_dms_endpoint.dms_endpoint_target.endpoint_arn}"
}
`, randId)
}
func dmsReplicationTaskConfigUpdate(randId string) string {
return fmt.Sprintf(`
resource "aws_iam_role" "dms_iam_role" {
name = "dms-vpc-role"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"dms.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
resource "aws_iam_role_policy_attachment" "dms_iam_role_policy" {
role = "${aws_iam_role.dms_iam_role.name}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonDMSVPCManagementRole"
}
resource "aws_vpc" "dms_vpc" {
cidr_block = "10.1.0.0/16"
tags {
Name = "tf-test-dms-vpc-%[1]s"
}
depends_on = ["aws_iam_role_policy_attachment.dms_iam_role_policy"]
}
resource "aws_subnet" "dms_subnet_1" {
cidr_block = "10.1.1.0/24"
availability_zone = "us-west-2a"
vpc_id = "${aws_vpc.dms_vpc.id}"
tags {
Name = "tf-test-dms-subnet-%[1]s"
}
depends_on = ["aws_vpc.dms_vpc"]
}
resource "aws_subnet" "dms_subnet_2" {
cidr_block = "10.1.2.0/24"
availability_zone = "us-west-2b"
vpc_id = "${aws_vpc.dms_vpc.id}"
tags {
Name = "tf-test-dms-subnet-%[1]s"
}
depends_on = ["aws_vpc.dms_vpc"]
}
resource "aws_dms_endpoint" "dms_endpoint_source" {
database_name = "tf-test-dms-db"
endpoint_id = "tf-test-dms-endpoint-source-%[1]s"
endpoint_type = "source"
engine_name = "aurora"
server_name = "tf-test-cluster.cluster-xxxxxxx.us-west-2.rds.amazonaws.com"
port = 3306
username = "tftest"
password = "tftest"
depends_on = ["aws_iam_role_policy_attachment.dms_iam_role_policy"]
}
resource "aws_dms_endpoint" "dms_endpoint_target" {
database_name = "tf-test-dms-db"
endpoint_id = "tf-test-dms-endpoint-target-%[1]s"
endpoint_type = "target"
engine_name = "aurora"
server_name = "tf-test-cluster.cluster-xxxxxxx.us-west-2.rds.amazonaws.com"
port = 3306
username = "tftest"
password = "tftest"
depends_on = ["aws_iam_role_policy_attachment.dms_iam_role_policy"]
}
resource "aws_dms_replication_subnet_group" "dms_replication_subnet_group" {
replication_subnet_group_id = "tf-test-dms-replication-subnet-group-%[1]s"
replication_subnet_group_description = "terraform test for replication subnet group"
subnet_ids = ["${aws_subnet.dms_subnet_1.id}", "${aws_subnet.dms_subnet_2.id}"]
depends_on = ["aws_iam_role_policy_attachment.dms_iam_role_policy"]
}
resource "aws_dms_replication_instance" "dms_replication_instance" {
allocated_storage = 5
auto_minor_version_upgrade = true
replication_instance_class = "dms.t2.micro"
replication_instance_id = "tf-test-dms-replication-instance-%[1]s"
preferred_maintenance_window = "sun:00:30-sun:02:30"
publicly_accessible = false
replication_subnet_group_id = "${aws_dms_replication_subnet_group.dms_replication_subnet_group.replication_subnet_group_id}"
}
resource "aws_dms_replication_task" "dms_replication_task" {
migration_type = "full-load"
replication_instance_arn = "${aws_dms_replication_instance.dms_replication_instance.replication_instance_arn}"
replication_task_id = "tf-test-dms-replication-task-%[1]s"
replication_task_settings = "{\"TargetMetadata\":{\"TargetSchema\":\"\",\"SupportLobs\":true,\"FullLobMode\":false,\"LobChunkSize\":0,\"LimitedSizeLobMode\":true,\"LobMaxSize\":32,\"LoadMaxFileSize\":0,\"ParallelLoadThreads\":0,\"BatchApplyEnabled\":false},\"FullLoadSettings\":{\"FullLoadEnabled\":true,\"ApplyChangesEnabled\":false,\"TargetTablePrepMode\":\"DROP_AND_CREATE\",\"CreatePkAfterFullLoad\":false,\"StopTaskCachedChangesApplied\":false,\"StopTaskCachedChangesNotApplied\":false,\"ResumeEnabled\":false,\"ResumeMinTableSize\":100000,\"ResumeOnlyClusteredPKTables\":true,\"MaxFullLoadSubTasks\":7,\"TransactionConsistencyTimeout\":600,\"CommitRate\":10000},\"Logging\":{\"EnableLogging\":false,\"LogComponents\":[{\"Id\":\"SOURCE_UNLOAD\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"},{\"Id\":\"TARGET_LOAD\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"},{\"Id\":\"SOURCE_CAPTURE\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"},{\"Id\":\"TARGET_APPLY\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"},{\"Id\":\"TASK_MANAGER\",\"Severity\":\"LOGGER_SEVERITY_DEFAULT\"}],\"CloudWatchLogGroup\":null,\"CloudWatchLogStream\":null},\"ControlTablesSettings\":{\"historyTimeslotInMinutes\":5,\"ControlSchema\":\"\",\"HistoryTimeslotInMinutes\":5,\"HistoryTableEnabled\":false,\"SuspendedTablesTableEnabled\":false,\"StatusTableEnabled\":false},\"StreamBufferSettings\":{\"StreamBufferCount\":3,\"StreamBufferSizeInMB\":8,\"CtrlStreamBufferSizeInMB\":5},\"ChangeProcessingDdlHandlingPolicy\":{\"HandleSourceTableDropped\":true,\"HandleSourceTableTruncated\":true,\"HandleSourceTableAltered\":true},\"ErrorBehavior\":{\"DataErrorPolicy\":\"LOG_ERROR\",\"DataTruncationErrorPolicy\":\"LOG_ERROR\",\"DataErrorEscalationPolicy\":\"SUSPEND_TABLE\",\"DataErrorEscalationCount\":0,\"TableErrorPolicy\":\"SUSPEND_TABLE\",\"TableErrorEscalationPolicy\":\"STOP_TASK\",\"TableErrorEscalationCount\":0,\"RecoverableErrorCount\":-1,\"RecoverableErrorInterval\":5,\"RecoverableErrorThrottling\":true,\"RecoverableErrorThrottlingMax\":1800,\"ApplyErrorDeletePolicy\":\"IGNORE_RECORD\",\"ApplyErrorInsertPolicy\":\"LOG_ERROR\",\"ApplyErrorUpdatePolicy\":\"LOG_ERROR\",\"ApplyErrorEscalationPolicy\":\"LOG_ERROR\",\"ApplyErrorEscalationCount\":0,\"FullLoadIgnoreConflicts\":true},\"ChangeProcessingTuning\":{\"BatchApplyPreserveTransaction\":true,\"BatchApplyTimeoutMin\":1,\"BatchApplyTimeoutMax\":30,\"BatchApplyMemoryLimit\":500,\"BatchSplitSize\":0,\"MinTransactionSize\":1000,\"CommitTimeout\":1,\"MemoryLimitTotal\":1024,\"MemoryKeepTime\":60,\"StatementCacheSize\":50}}"
source_endpoint_arn = "${aws_dms_endpoint.dms_endpoint_source.endpoint_arn}"
table_mappings = "{\"rules\":[{\"rule-type\":\"selection\",\"rule-id\":\"1\",\"rule-name\":\"1\",\"object-locator\":{\"schema-name\":\"%%\",\"table-name\":\"%%\"},\"rule-action\":\"include\"}]}"
tags {
Name = "tf-test-dms-replication-task-%[1]s"
Update = "updated"
Add = "added"
}
target_endpoint_arn = "${aws_dms_endpoint.dms_endpoint_target.endpoint_arn}"
}
`, randId)
}

View File

@ -0,0 +1,91 @@
package aws
import (
"github.com/aws/aws-sdk-go/aws"
dms "github.com/aws/aws-sdk-go/service/databasemigrationservice"
"github.com/hashicorp/terraform/helper/schema"
)
func dmsTagsToMap(tags []*dms.Tag) map[string]string {
result := make(map[string]string)
for _, tag := range tags {
result[*tag.Key] = *tag.Value
}
return result
}
func dmsTagsFromMap(m map[string]interface{}) []*dms.Tag {
result := make([]*dms.Tag, 0, len(m))
for k, v := range m {
result = append(result, &dms.Tag{
Key: aws.String(k),
Value: aws.String(v.(string)),
})
}
return result
}
func dmsDiffTags(oldTags, newTags []*dms.Tag) ([]*dms.Tag, []*dms.Tag) {
create := make(map[string]interface{})
for _, t := range newTags {
create[*t.Key] = *t.Value
}
remove := []*dms.Tag{}
for _, t := range oldTags {
v, ok := create[*t.Key]
if !ok || v != *t.Value {
remove = append(remove, t)
}
}
return dmsTagsFromMap(create), remove
}
func dmsGetTagKeys(tags []*dms.Tag) []*string {
keys := []*string{}
for _, tag := range tags {
keys = append(keys, tag.Key)
}
return keys
}
func dmsSetTags(arn string, d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn
if d.HasChange("tags") {
oraw, nraw := d.GetChange("tags")
o := oraw.(map[string]interface{})
n := nraw.(map[string]interface{})
add, remove := dmsDiffTags(dmsTagsFromMap(o), dmsTagsFromMap(n))
if len(remove) > 0 {
_, err := conn.RemoveTagsFromResource(&dms.RemoveTagsFromResourceInput{
ResourceArn: aws.String(arn),
TagKeys: dmsGetTagKeys(remove),
})
if err != nil {
return err
}
}
if len(add) > 0 {
_, err := conn.AddTagsToResource(&dms.AddTagsToResourceInput{
ResourceArn: aws.String(arn),
Tags: add,
})
if err != nil {
return err
}
}
}
return nil
}

View File

@ -0,0 +1,114 @@
package aws
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
dms "github.com/aws/aws-sdk-go/service/databasemigrationservice"
"reflect"
)
func TestDmsTagsToMap(t *testing.T) {
tags := []*dms.Tag{
{
Key: aws.String("test-key-1"),
Value: aws.String("test-value-1"),
},
{
Key: aws.String("test-key-2"),
Value: aws.String("test-value-2"),
},
}
result := dmsTagsToMap(tags)
for _, tag := range tags {
if v, ok := result[*tag.Key]; ok {
if v != *tag.Value {
t.Fatalf("Key %s had value of %s. Expected %s.", *tag.Key, v, *tag.Value)
}
} else {
t.Fatalf("Key %s not in map.", *tag.Key)
}
}
}
func TestDmsTagsFromMap(t *testing.T) {
tagMap := map[string]interface{}{
"test-key-1": "test-value-1",
"test-key-2": "test-value-2",
}
result := dmsTagsFromMap(tagMap)
for k, v := range tagMap {
found := false
for _, tag := range result {
if k == *tag.Key {
if v != *tag.Value {
t.Fatalf("Key %s had value of %s. Expected %s.", k, v, *tag.Value)
}
found = true
break
}
}
if !found {
t.Fatalf("Key %s not in tags.", k)
}
}
}
func TestDmsDiffTags(t *testing.T) {
cases := []struct {
o, n map[string]interface{}
a, r map[string]string
}{
// basic add / remove
{
o: map[string]interface{}{"test-key-1": "test-value-1"},
n: map[string]interface{}{"test-key-2": "test-value-2"},
a: map[string]string{"test-key-2": "test-value-2"},
r: map[string]string{"test-key-1": "test-value-1"},
},
// modify
{
o: map[string]interface{}{"test-key-1": "test-value-1"},
n: map[string]interface{}{"test-key-1": "test-value-1-modified"},
a: map[string]string{"test-key-1": "test-value-1-modified"},
r: map[string]string{"test-key-1": "test-value-1"},
},
}
for _, c := range cases {
ar, rr := dmsDiffTags(dmsTagsFromMap(c.o), dmsTagsFromMap(c.n))
a := dmsTagsToMap(ar)
r := dmsTagsToMap(rr)
if !reflect.DeepEqual(a, c.a) {
t.Fatalf("Add tags mismatch: Actual %#v; Expected %#v", a, c.a)
}
if !reflect.DeepEqual(r, c.r) {
t.Fatalf("Remove tags mismatch: Actual %#v; Expected %#v", r, c.r)
}
}
}
func TestDmsGetTagKeys(t *testing.T) {
tags := []*dms.Tag{
{
Key: aws.String("test-key-1"),
Value: aws.String("test-value-1"),
},
{
Key: aws.String("test-key-2"),
Value: aws.String("test-value-2"),
},
}
result := dmsGetTagKeys(tags)
expected := []*string{aws.String("test-key-1"), aws.String("test-key-2")}
if !reflect.DeepEqual(result, expected) {
t.Fatalf("Actual %s; Expected %s", aws.StringValueSlice(result), aws.StringValueSlice(expected))
}
}

View File

@ -774,6 +774,95 @@ func validateSfnStateMachineName(v interface{}, k string) (ws []string, errors [
return
}
func validateDmsCertificateId(v interface{}, k string) (ws []string, es []error) {
val := v.(string)
if len(val) > 255 {
es = append(es, fmt.Errorf("%q must not be longer than 255 characters", k))
}
if !regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9-]+$").MatchString(val) {
es = append(es, fmt.Errorf("%q must start with a letter, only contain alphanumeric characters and hyphens", k))
}
if strings.Contains(val, "--") {
es = append(es, fmt.Errorf("%q must not contain consecutive hyphens", k))
}
if strings.HasSuffix(val, "-") {
es = append(es, fmt.Errorf("%q must not end in a hyphen", k))
}
return
}
func validateDmsEndpointId(v interface{}, k string) (ws []string, es []error) {
val := v.(string)
if len(val) > 255 {
es = append(es, fmt.Errorf("%q must not be longer than 255 characters", k))
}
if !regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9-]+$").MatchString(val) {
es = append(es, fmt.Errorf("%q must start with a letter, only contain alphanumeric characters and hyphens", k))
}
if strings.Contains(val, "--") {
es = append(es, fmt.Errorf("%q must not contain consecutive hyphens", k))
}
if strings.HasSuffix(val, "-") {
es = append(es, fmt.Errorf("%q must not end in a hyphen", k))
}
return
}
func validateDmsReplicationInstanceId(v interface{}, k string) (ws []string, es []error) {
val := v.(string)
if len(val) > 63 {
es = append(es, fmt.Errorf("%q must not be longer than 63 characters", k))
}
if !regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9-]+$").MatchString(val) {
es = append(es, fmt.Errorf("%q must start with a letter, only contain alphanumeric characters and hyphens", k))
}
if strings.Contains(val, "--") {
es = append(es, fmt.Errorf("%q must not contain consecutive hyphens", k))
}
if strings.HasSuffix(val, "-") {
es = append(es, fmt.Errorf("%q must not end in a hyphen", k))
}
return
}
func validateDmsReplicationSubnetGroupId(v interface{}, k string) (ws []string, es []error) {
val := v.(string)
if val == "default" {
es = append(es, fmt.Errorf("%q must not be default", k))
}
if len(val) > 255 {
es = append(es, fmt.Errorf("%q must not be longer than 255 characters", k))
}
if !regexp.MustCompile(`^[a-zA-Z0-9. _-]+$`).MatchString(val) {
es = append(es, fmt.Errorf("%q must only contain alphanumeric characters, periods, spaces, underscores and hyphens", k))
}
return
}
func validateDmsReplicationTaskId(v interface{}, k string) (ws []string, es []error) {
val := v.(string)
if len(val) > 255 {
es = append(es, fmt.Errorf("%q must not be longer than 255 characters", k))
}
if !regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9-]+$").MatchString(val) {
es = append(es, fmt.Errorf("%q must start with a letter, only contain alphanumeric characters and hyphens", k))
}
if strings.Contains(val, "--") {
es = append(es, fmt.Errorf("%q must not contain consecutive hyphens", k))
}
if strings.HasSuffix(val, "-") {
es = append(es, fmt.Errorf("%q must not end in a hyphen", k))
}
func validateAppautoscalingScalableDimension(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
dimensions := map[string]bool{

View File

@ -1386,3 +1386,157 @@ func TestValidateAppautoscalingServiceNamespace(t *testing.T) {
}
}
}
func TestValidateDmsEndpointId(t *testing.T) {
validIds := []string{
"tf-test-endpoint-1",
"tfTestEndpoint",
}
for _, s := range validIds {
_, errors := validateDmsEndpointId(s, "endpoint_id")
if len(errors) > 0 {
t.Fatalf("%q should be a valid endpoint id: %v", s, errors)
}
}
invalidIds := []string{
"tf_test_endpoint_1",
"tf.test.endpoint.1",
"tf test endpoint 1",
"tf-test-endpoint-1!",
"tf-test-endpoint-1-",
"tf-test-endpoint--1",
"tf-test-endpoint-1tf-test-endpoint-1tf-test-endpoint-1tf-test-endpoint-1tf-test-endpoint-1tf-test-endpoint-1tf-test-endpoint-1tf-test-endpoint-1tf-test-endpoint-1tf-test-endpoint-1tf-test-endpoint-1tf-test-endpoint-1tf-test-endpoint-1tf-test-endpoint-1tf-test-endpoint-1",
}
for _, s := range invalidIds {
_, errors := validateDmsEndpointId(s, "endpoint_id")
if len(errors) == 0 {
t.Fatalf("%q should not be a valid endpoint id: %v", s, errors)
}
}
}
func TestValidateDmsCertificateId(t *testing.T) {
validIds := []string{
"tf-test-certificate-1",
"tfTestEndpoint",
}
for _, s := range validIds {
_, errors := validateDmsCertificateId(s, "certificate_id")
if len(errors) > 0 {
t.Fatalf("%q should be a valid certificate id: %v", s, errors)
}
}
invalidIds := []string{
"tf_test_certificate_1",
"tf.test.certificate.1",
"tf test certificate 1",
"tf-test-certificate-1!",
"tf-test-certificate-1-",
"tf-test-certificate--1",
"tf-test-certificate-1tf-test-certificate-1tf-test-certificate-1tf-test-certificate-1tf-test-certificate-1tf-test-certificate-1tf-test-certificate-1tf-test-certificate-1tf-test-certificate-1tf-test-certificate-1tf-test-certificate-1tf-test-certificate-1tf-test-certificate-1tf-test-certificate-1tf-test-certificate-1",
}
for _, s := range invalidIds {
_, errors := validateDmsEndpointId(s, "certificate_id")
if len(errors) == 0 {
t.Fatalf("%q should not be a valid certificate id: %v", s, errors)
}
}
}
func TestValidateDmsReplicationInstanceId(t *testing.T) {
validIds := []string{
"tf-test-replication-instance-1",
"tfTestReplicaitonInstance",
}
for _, s := range validIds {
_, errors := validateDmsReplicationInstanceId(s, "replicaiton_instance_id")
if len(errors) > 0 {
t.Fatalf("%q should be a valid replication instance id: %v", s, errors)
}
}
invalidIds := []string{
"tf_test_replication-instance_1",
"tf.test.replication.instance.1",
"tf test replication instance 1",
"tf-test-replication-instance-1!",
"tf-test-replication-instance-1-",
"tf-test-replication-instance--1",
"tf-test-replication-instance-1tf-test-replication-instance-1tf-test-replication-instance-1",
}
for _, s := range invalidIds {
_, errors := validateDmsReplicationInstanceId(s, "replication_instance_id")
if len(errors) == 0 {
t.Fatalf("%q should not be a valid replication instance id: %v", s, errors)
}
}
}
func TestValidateDmsReplicationSubnetGroupId(t *testing.T) {
validIds := []string{
"tf-test-replication-subnet-group-1",
"tf_test_replication_subnet_group_1",
"tf.test.replication.subnet.group.1",
"tf test replication subnet group 1",
"tfTestReplicationSubnetGroup",
}
for _, s := range validIds {
_, errors := validateDmsReplicationSubnetGroupId(s, "replication_subnet_group_id")
if len(errors) > 0 {
t.Fatalf("%q should be a valid replication subnet group id: %v", s, errors)
}
}
invalidIds := []string{
"default",
"tf-test-replication-subnet-group-1!",
"tf-test-replication-subnet-group-1tf-test-replication-subnet-group-1tf-test-replication-subnet-group-1tf-test-replication-subnet-group-1tf-test-replication-subnet-group-1tf-test-replication-subnet-group-1tf-test-replication-subnet-group-1tf-test-replication-subnet-group-1",
}
for _, s := range invalidIds {
_, errors := validateDmsReplicationSubnetGroupId(s, "replication_subnet_group_id")
if len(errors) == 0 {
t.Fatalf("%q should not be a valid replication subnet group id: %v", s, errors)
}
}
}
func TestValidateDmsReplicationTaskId(t *testing.T) {
validIds := []string{
"tf-test-replication-task-1",
"tfTestReplicationTask",
}
for _, s := range validIds {
_, errors := validateDmsReplicationTaskId(s, "replication_task_id")
if len(errors) > 0 {
t.Fatalf("%q should be a valid replication task id: %v", s, errors)
}
}
invalidIds := []string{
"tf_test_replication_task_1",
"tf.test.replication.task.1",
"tf test replication task 1",
"tf-test-replication-task-1!",
"tf-test-replication-task-1-",
"tf-test-replication-task--1",
"tf-test-replication-task-1tf-test-replication-task-1tf-test-replication-task-1tf-test-replication-task-1tf-test-replication-task-1tf-test-replication-task-1tf-test-replication-task-1tf-test-replication-task-1tf-test-replication-task-1tf-test-replication-task-1",
}
for _, s := range invalidIds {
_, errors := validateDmsReplicationTaskId(s, "replication_task_id")
if len(errors) == 0 {
t.Fatalf("%q should not be a valid replication task id: %v", s, errors)
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,99 @@
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
package databasemigrationservice
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/client/metadata"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/signer/v4"
"github.com/aws/aws-sdk-go/private/protocol/jsonrpc"
)
// AWS Database Migration Service (AWS DMS) can migrate your data to and from
// the most widely used commercial and open-source databases such as Oracle,
// PostgreSQL, Microsoft SQL Server, Amazon Redshift, MariaDB, Amazon Aurora,
// MySQL, and SAP Adaptive Server Enterprise (ASE). The service supports homogeneous
// migrations such as Oracle to Oracle, as well as heterogeneous migrations
// between different database platforms, such as Oracle to MySQL or SQL Server
// to PostgreSQL.
// The service client's operations are safe to be used concurrently.
// It is not safe to mutate any of the client's properties though.
// Please also see https://docs.aws.amazon.com/goto/WebAPI/dms-2016-01-01
type DatabaseMigrationService struct {
*client.Client
}
// Used for custom client initialization logic
var initClient func(*client.Client)
// Used for custom request initialization logic
var initRequest func(*request.Request)
// Service information constants
const (
ServiceName = "dms" // Service endpoint prefix API calls made to.
EndpointsID = ServiceName // Service ID for Regions and Endpoints metadata.
)
// New creates a new instance of the DatabaseMigrationService client with a session.
// If additional configuration is needed for the client instance use the optional
// aws.Config parameter to add your extra config.
//
// Example:
// // Create a DatabaseMigrationService client from just a session.
// svc := databasemigrationservice.New(mySession)
//
// // Create a DatabaseMigrationService client with additional configuration
// svc := databasemigrationservice.New(mySession, aws.NewConfig().WithRegion("us-west-2"))
func New(p client.ConfigProvider, cfgs ...*aws.Config) *DatabaseMigrationService {
c := p.ClientConfig(EndpointsID, cfgs...)
return newClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion, c.SigningName)
}
// newClient creates, initializes and returns a new service client instance.
func newClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion, signingName string) *DatabaseMigrationService {
svc := &DatabaseMigrationService{
Client: client.New(
cfg,
metadata.ClientInfo{
ServiceName: ServiceName,
SigningName: signingName,
SigningRegion: signingRegion,
Endpoint: endpoint,
APIVersion: "2016-01-01",
JSONVersion: "1.1",
TargetPrefix: "AmazonDMSv20160101",
},
handlers,
),
}
// Handlers
svc.Handlers.Sign.PushBackNamed(v4.SignRequestHandler)
svc.Handlers.Build.PushBackNamed(jsonrpc.BuildHandler)
svc.Handlers.Unmarshal.PushBackNamed(jsonrpc.UnmarshalHandler)
svc.Handlers.UnmarshalMeta.PushBackNamed(jsonrpc.UnmarshalMetaHandler)
svc.Handlers.UnmarshalError.PushBackNamed(jsonrpc.UnmarshalErrorHandler)
// Run custom client initialization if present
if initClient != nil {
initClient(svc.Client)
}
return svc
}
// newRequest creates a new request for a DatabaseMigrationService operation and runs any
// custom request initialization.
func (c *DatabaseMigrationService) newRequest(op *request.Operation, params, data interface{}) *request.Request {
req := c.NewRequest(op, params, data)
// Run custom request initialization if present
if initRequest != nil {
initRequest(req)
}
return req
}

8
vendor/vendor.json vendored
View File

@ -788,6 +788,14 @@
"version": "v1.6.18",
"versionExact": "v1.6.18"
},
{
"checksumSHA1": "xKUp9QOvMJOavwFwnbLcqq1FxOE=",
"path": "github.com/aws/aws-sdk-go/service/databasemigrationservice",
"revision": "f82d132783af109928d89d526ccc8d9fc320639e",
"revisionTime": "2017-01-19T23:36:13Z",
"version": "v1.6.14",
"versionExact": "v1.6.14"
},
{
"checksumSHA1": "W29S9x3pVL3xha1nsGqGtSQtGwA=",
"path": "github.com/aws/aws-sdk-go/service/directoryservice",

View File

@ -41,6 +41,11 @@ To make a resource importable, please see the
* aws_db_parameter_group
* aws_db_security_group
* aws_db_subnet_group
* aws_dms_certificate
* aws_dms_endpoint
* aws_dms_replication_instance
* aws_dms_replication_subnet_group
* aws_dms_replication_task
* aws_dynamodb_table
* aws_ebs_volume
* aws_ecr_repository

View File

@ -0,0 +1,46 @@
---
layout: "aws"
page_title: "AWS: aws_dms_certificate"
sidebar_current: "docs-aws-resource-dms-certificate"
description: |-
Provides a DMS (Data Migration Service) certificate resource.
---
# aws\_dms\_certificate
Provides a DMS (Data Migration Service) certificate resource. DMS certificates can be created, deleted, and imported.
## Example Usage
```
# Create a new certificate
resource "aws_dms_certificate" "test" {
certificate_id = "test-dms-certificate-tf"
certificate_pem = "..."
}
```
## Argument Reference
The following arguments are supported:
* `certificate_id` - (Required) The certificate identifier.
- Must contain from 1 to 255 alphanumeric characters and hyphens.
* `certificate_pem` - (Optional) The contents of the .pem X.509 certificate file for the certificate. Either `certificate_pem` or `certificate_wallet` must be set.
* `certificate_wallet` - (Optional) The contents of the Oracle Wallet certificate for use with SSL. Either `certificate_pem` or `certificate_wallet` must be set.
## Attributes Reference
The following attributes are exported:
* `certificate_arn` - The Amazon Resource Name (ARN) for the certificate.
## Import
Certificates can be imported using the `certificate_arn`, e.g.
```
$ terraform import aws_dms_certificate.test arn:aws:dms:us-west-2:123456789:cert:xxxxxxxxxx
```

View File

@ -0,0 +1,73 @@
---
layout: "aws"
page_title: "AWS: aws_dms_endpoint"
sidebar_current: "docs-aws-resource-dms-endpoint"
description: |-
Provides a DMS (Data Migration Service) endpoint resource.
---
# aws\_dms\_endpoint
Provides a DMS (Data Migration Service) endpoint resource. DMS endpoints can be created, updated, deleted, and imported.
## Example Usage
```
# Create a new endpoint
resource "aws_dms_endpoint" "test" {
certificate_arn = "arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012"
database_name = "test"
endpoint_id = "test-dms-endpoint-tf"
endpoint_type = "source"
engine_name = "aurora"
extra_connection_attributes = ""
kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
password = "test"
port = 3306
server_name = "test"
ssl_mode = "none"
tags {
Name = "test"
}
username = "test"
}
```
## Argument Reference
The following arguments are supported:
* `certificate_arn` - (Optional, Default: empty string) The Amazon Resource Name (ARN) for the certificate.
* `database_name` - (Optional, Default: empty string) The name of the endpoint database.
* `endpoint_id` - (Required) The database endpoint identifier.
- Must contain from 1 to 255 alphanumeric characters or hyphens.
- Must begin with a letter
- Must contain only ASCII letters, digits, and hyphens
- Must not end with a hyphen
- Must not contain two consecutive hyphens
* `endpoint_type` - (Required) The type of endpoint. Can be one of `source | target`.
* `engine_name` - (Required) The type of engine for the endpoint. Can be one of `mysql | oracle | postgres | mariadb | aurora | redshift | sybase | sqlserver`.
* `extra_connection_attributes` - (Optional) Additional attributes associated with the connection. For available attributes see [Using Extra Connection Attributes with AWS Database Migration Service](http://docs.aws.amazon.com/dms/latest/userguide/CHAP_Introduction.ConnectionAttributes.html).
* `kms_key_arn` - (Optional) The Amazon Resource Name (ARN) for the KMS key that will be used to encrypt the connection parameters. If you do not specify a value for `kms_key_arn`, then AWS DMS will use your default encryption key. AWS KMS creates the default encryption key for your AWS account. Your AWS account has a different default encryption key for each AWS region.
* `password` - (Required) The password to be used to login to the endpoint database.
* `port` - (Required) The port used by the endpoint database.
* `server_name` - (Required) The host name of the server.
* `ssl_mode` - (Optional, Default: none) The SSL mode to use for the connection. Can be one of `none | require | verify-ca | verify-full`
* `tags` - (Optional) A mapping of tags to assign to the resource.
* `username` - (Required) The user name to be used to login to the endpoint database.
## Attributes Reference
The following attributes are exported:
* `endpoint_arn` - The Amazon Resource Name (ARN) for the endpoint.
## Import
Endpoints can be imported using the `endpoint_id`, e.g.
```
$ terraform import aws_dms_endpoint.test test-dms-endpoint-tf
```

View File

@ -0,0 +1,84 @@
---
layout: "aws"
page_title: "AWS: aws_dms_replication_instance"
sidebar_current: "docs-aws-resource-dms-replication-instance"
description: |-
Provides a DMS (Data Migration Service) replication instance resource.
---
# aws\_dms\_replication\_instance
Provides a DMS (Data Migration Service) replication instance resource. DMS replication instances can be created, updated, deleted, and imported.
## Example Usage
```
# Create a new replication instance
resource "aws_dms_replication_instance" "test" {
allocated_storage = 20
apply_immediately = true
auto_minor_version_upgrade = true
availability_zone = "us-west-2c"
engine_version = "1.9.0"
kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
multi_az = false
preferred_maintenance_window = "sun:10:30-sun:14:30"
publicly_accessible = true
replication_instance_class = "dms.t2.micro"
replication_instance_id = "test-dms-replication-instance-tf"
replication_subnet_group_id = "${aws_dms_replication_subnet_group.test-dms-replication-subnet-group-tf}"
tags {
Name = "test"
}
vpc_security_group_ids = [
"sg-12345678",
]
}
```
## Argument Reference
The following arguments are supported:
* `allocated_storage` - (Optional, Default: 50, Min: 5, Max: 6144) The amount of storage (in gigabytes) to be initially allocated for the replication instance.
* `apply_immediately` - (Optional, Default: false) Indicates whether the changes should be applied immediately or during the next maintenance window. Only used when updating an existing resource.
* `auto_minor_version_upgrade` - (Optional, Default: false) Indicates that minor engine upgrades will be applied automatically to the replication instance during the maintenance window.
* `availability_zone` - (Optional) The EC2 Availability Zone that the replication instance will be created in.
* `engine_version` - (Optional) The engine version number of the replication instance.
* `kms_key_arn` - (Optional) The Amazon Resource Name (ARN) for the KMS key that will be used to encrypt the connection parameters. If you do not specify a value for `kms_key_arn`, then AWS DMS will use your default encryption key. AWS KMS creates the default encryption key for your AWS account. Your AWS account has a different default encryption key for each AWS region.
* `multi_az` - (Optional) Specifies if the replication instance is a multi-az deployment. You cannot set the `availability_zone` parameter if the `multi_az` parameter is set to `true`.
* `preferred_maintenance_window` - (Optional) The weekly time range during which system maintenance can occur, in Universal Coordinated Time (UTC).
- Default: A 30-minute window selected at random from an 8-hour block of time per region, occurring on a random day of the week.
- Format: `ddd:hh24:mi-ddd:hh24:mi`
- Valid Days: `mon, tue, wed, thu, fri, sat, sun`
- Constraints: Minimum 30-minute window.
* `publicly_accessible` - (Optional, Default: false) Specifies the accessibility options for the replication instance. A value of true represents an instance with a public IP address. A value of false represents an instance with a private IP address.
* `replication_instance_class` - (Required) The compute and memory capacity of the replication instance as specified by the replication instance class. Can be one of `dms.t2.micro | dms.t2.small | dms.t2.medium | dms.t2.large | dms.c4.large | dms.c4.xlarge | dms.c4.2xlarge | dms.c4.4xlarge`
* `replication_instance_id` - (Required) The replication instance identifier. This parameter is stored as a lowercase string.
- Must contain from 1 to 63 alphanumeric characters or hyphens.
- First character must be a letter.
- Cannot end with a hyphen
- Cannot contain two consecutive hyphens.
* `replication_subnet_group_id` - (Optional) A subnet group to associate with the replication instance.
* `tags` - (Optional) A mapping of tags to assign to the resource.
* `vpc_security_group_ids` - (Optional) A list of VPC security group IDs to be used with the replication instance. The VPC security groups must work with the VPC containing the replication instance.
## Attributes Reference
The following attributes are exported:
* `replication_instance_arn` - The Amazon Resource Name (ARN) of the replication instance.
* `replication_instance_private_ips` - A list of the private IP addresses of the replication instance.
* `replication_instance_public_ips` - A list of the public IP addresses of the replication instance.
## Import
Replication instances can be imported using the `replication_instance_id`, e.g.
```
$ terraform import aws_dms_replication_instance.test test-dms-replication-instance-tf
```

View File

@ -0,0 +1,50 @@
---
layout: "aws"
page_title: "AWS: aws_dms_replication_subnet_group"
sidebar_current: "docs-aws-resource-dms-replication-subnet-group"
description: |-
Provides a DMS (Data Migration Service) subnet group resource.
---
# aws\_dms\_replication\_subnet\_group
Provides a DMS (Data Migration Service) replication subnet group resource. DMS replication subnet groups can be created, updated, deleted, and imported.
## Example Usage
```
# Create a new replication subnet group
resource "aws_dms_replication_subnet_group" "test" {
replication_subnet_group_description = "Test replication subnet group"
replication_subnet_group_id = "test-dms-replication-subnet-group-tf"
subnet_ids = [
"subnet-12345678",
]
}
```
## Argument Reference
The following arguments are supported:
* `replication_subnet_group_description` - (Required) The description for the subnet group.
* `replication_subnet_group_id` - (Required) The name for the replication subnet group. This value is stored as a lowercase string.
- Must contain no more than 255 alphanumeric characters, periods, spaces, underscores, or hyphens.
- Must not be "default".
* `subnet_ids` - (Required) A list of the EC2 subnet IDs for the subnet group.
## Attributes Reference
The following attributes are exported:
* `vpc_id` - The ID of the VPC the subnet group is in.
## Import
Replication subnet groups can be imported using the `replication_subnet_group_id`, e.g.
```
$ terraform import aws_dms_replication_subnet_group.test test-dms-replication-subnet-group-tf
```

View File

@ -0,0 +1,64 @@
---
layout: "aws"
page_title: "AWS: aws_dms_replication_task"
sidebar_current: "docs-aws-resource-dms-replication-task"
description: |-
Provides a DMS (Data Migration Service) replication task resource.
---
# aws\_dms\_replication\_task
Provides a DMS (Data Migration Service) replication task resource. DMS replication tasks can be created, updated, deleted, and imported.
## Example Usage
```
# Create a new replication task
resource "aws_dms_replication_task" "test" {
cdc_start_time = 1484346880
migration_type = "full-load"
replication_instance_arn = "${aws_dms_replication_instance.test-dms-replication-instance-tf.replication_instance_arn}"
replication_task_id = "test-dms-replication-task-tf"
replication_task_settings = "..."
source_endpoint_arn = "${aws_dms_endpoint.test-dms-source-endpoint-tf.endpoint_arn}"
table_mappings = "{\"rules\":[{\"rule-type\":\"selection\",\"rule-id\":\"1\",\"rule-name\":\"1\",\"object-locator\":{\"schema-name\":\"%\",\"table-name\":\"%\"},\"rule-action\":\"include\"}]}"
tags {
Name = "test"
}
target_endpoint_arn = "${aws_dms_endpoint.test-dms-target-endpoint-tf.endpoint_arn}"
}
```
## Argument Reference
The following arguments are supported:
* `cdc_start_time` - (Optional) The Unix timestamp integer for the start of the Change Data Capture (CDC) operation.
* `migration_type` - (Required) The migration type. Can be one of `full-load | cdc | full-load-and-cdc`.
* `replication_instance_arn` - (Required) The Amazon Resource Name (ARN) of the replication instance.
* `replication_task_id` - (Required) The replication task identifier.
- Must contain from 1 to 255 alphanumeric characters or hyphens.
- First character must be a letter.
- Cannot end with a hyphen.
- Cannot contain two consecutive hyphens.
* `replication_task_settings` - (Optional) An escaped JSON string that contains the task settings. For a complete list of task settings, see [Task Settings for AWS Database Migration Service Tasks](http://docs.aws.amazon.com/dms/latest/userguide/CHAP_Tasks.CustomizingTasks.TaskSettings.html).
* `source_endpoint_arn` - (Required) The Amazon Resource Name (ARN) string that uniquely identifies the source endpoint.
* `table_mappings` - (Required) An escaped JSON string that contains the table mappings. For information on table mapping see [Using Table Mapping with an AWS Database Migration Service Task to Select and Filter Data](http://docs.aws.amazon.com/dms/latest/userguide/CHAP_Tasks.CustomizingTasks.TableMapping.html)
* `tags` - (Optional) A mapping of tags to assign to the resource.
* `target_endpoint_arn` - (Required) The Amazon Resource Name (ARN) string that uniquely identifies the target endpoint.
## Attributes Reference
The following attributes are exported:
* `replication_task_arn` - The Amazon Resource Name (ARN) for the replication task.
## Import
Replication tasks can be imported using the `replication_task_id`, e.g.
```
$ terraform import aws_dms_replication_task.test test-dms-replication-task-tf
```

View File

@ -278,6 +278,33 @@
</ul>
</li>
<li<%= sidebar_current(/^docs-aws-resource-dms/) %>>
<a href="#">Database Migration Service</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-aws-resource-dms-certificate") %>>
<a href="/docs/providers/aws/r/dms_certificate.html">aws_dms_certificate</a>
</li>
<li<%= sidebar_current("docs-aws-resource-dms-endpoint") %>>
<a href="/docs/providers/aws/r/dms_endpoint.html">aws_dms_endpoint</a>
</li>
<li<%= sidebar_current("docs-aws-resource-dms-replication-instance") %>>
<a href="/docs/providers/aws/r/dms_replication_instance.html">aws_dms_replication_instance</a>
</li>
<li<%= sidebar_current("docs-aws-resource-dms-replication-subnet-group") %>>
<a href="/docs/providers/aws/r/dms_replication_subnet_group.html">aws_dms_replication_subnet_group</a>
</li>
<li<%= sidebar_current("docs-aws-resource-dms-replication-task") %>>
<a href="/docs/providers/aws/r/dms_replication_task.html">aws_dms_replication_task</a>
</li>
</ul>
</li>
<li<%= sidebar_current(/^docs-aws-resource-directory-service/) %>>
<a href="#">Directory Service Resources</a>
<ul class="nav nav-visible">