Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dms_event_subscription resource #7170

Merged
merged 1 commit into from
Apr 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ func Provider() terraform.ResourceProvider {
"aws_dlm_lifecycle_policy": resourceAwsDlmLifecyclePolicy(),
"aws_dms_certificate": resourceAwsDmsCertificate(),
"aws_dms_endpoint": resourceAwsDmsEndpoint(),
"aws_dms_event_subscription": resourceAwsDmsEventSubscription(),
"aws_dms_replication_instance": resourceAwsDmsReplicationInstance(),
"aws_dms_replication_subnet_group": resourceAwsDmsReplicationSubnetGroup(),
"aws_dms_replication_task": resourceAwsDmsReplicationTask(),
Expand Down
251 changes: 251 additions & 0 deletions aws/resource_aws_dms_event_subscription.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
package aws

import (
"fmt"
"log"
"time"

"github.com/aws/aws-sdk-go/aws"
dms "github.com/aws/aws-sdk-go/service/databasemigrationservice"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
Comment on lines +10 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this pull request has been introduced, we have moved to the standalone Terraform Plugin SDK repository (this will need to be fixed in all files here):

Suggested change
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"

)

func resourceAwsDmsEventSubscription() *schema.Resource {
return &schema.Resource{
Create: resourceAwsDmsEventSubscriptionCreate,
Read: resourceAwsDmsEventSubscriptionRead,
Update: resourceAwsDmsEventSubscriptionUpdate,
Delete: resourceAwsDmsEventSubscriptionDelete,
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(10 * time.Minute),
Delete: schema.DefaultTimeout(10 * time.Minute),
Update: schema.DefaultTimeout(10 * time.Minute),
},

Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"enabled": {
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"event_categories": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
Required: true,
},
"name": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: We can provide simple plan-time validation for this attribute via ValidateFunc: validation.StringLenBetween(1, 255) 👍

Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"sns_topic_arn": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: We can provide simple plan-time validation for this attribute via ValidateFunc: validateArn 👍

Type: schema.TypeString,
Required: true,
},
"source_ids": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
ForceNew: true,
Optional: true,
},
"source_type": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: We can provide simple plan-time validation for this attribute via the below 👍

ValidateFunc: validation.StringInSlice([]string{
  "replication-instance",
  "replication-task",
}, false),

Type: schema.TypeString,
Optional: true,
// The API suppors modification but doing so loses all source_ids
ForceNew: true,
},
"tags": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the introduction of this pull request, tagging support in the provider codebase has moved to a shared package, keyvaluetags: #10688

The issue description highlights all the steps to refactor this functionality, although notably it looks like the Read and Update functions below are missing tags handling, so that will need to be added. The Contributing Guide section on Adding Resource Tagging Support shows how to add acceptance testing for this.

Type: schema.TypeMap,
Optional: true,
},
},
}
}

func resourceAwsDmsEventSubscriptionCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn

request := &dms.CreateEventSubscriptionInput{
Enabled: aws.Bool(d.Get("enabled").(bool)),
SnsTopicArn: aws.String(d.Get("sns_topic_arn").(string)),
SubscriptionName: aws.String(d.Get("name").(string)),
SourceType: aws.String(d.Get("source_type").(string)),
Tags: dmsTagsFromMap(d.Get("tags").(map[string]interface{})),
}

if v, ok := d.GetOk("event_categories"); ok {
request.EventCategories = expandStringList(v.(*schema.Set).List())
}

if v, ok := d.GetOk("source_ids"); ok {
request.SourceIds = expandStringList(v.(*schema.Set).List())
}

_, err := conn.CreateEventSubscription(request)

if err != nil {
return fmt.Errorf("Error creating DMS event subscription: %s", err)
}

d.SetId(d.Get("name").(string))

log.Println("[DEBUG] DMS create event subscription", request)

stateConf := &resource.StateChangeConf{
Pending: []string{"creating", "modifying"},
Target: []string{"active"},
Refresh: resourceAwsDmsEventSubscriptionStateRefreshFunc(conn, d.Id()),
Timeout: d.Timeout(schema.TimeoutCreate),
MinTimeout: 10 * time.Second,
Delay: 10 * time.Second,
}

// Wait, catching any errors
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("error waiting for DMS event subscription (%s) creation: %s", d.Id(), err)
}

return resourceAwsDmsEventSubscriptionUpdate(d, meta)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Terraform AWS Provider resources should prefer to call Read after Create, generally to prevent duplicate API calls.

Suggested change
return resourceAwsDmsEventSubscriptionUpdate(d, meta)
return resourceAwsDmsEventSubscriptionRead(d, meta)

}

func resourceAwsDmsEventSubscriptionUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn

request := &dms.ModifyEventSubscriptionInput{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To support tags updates and this API call separately, the existing update code will need to be wrapped in:

if d.HasChanges("enabled", "event_categories", "sns_topic_arn", "source_type") {
  // ... existing update code ...
}

Enabled: aws.Bool(d.Get("enabled").(bool)),
SnsTopicArn: aws.String(d.Get("sns_topic_arn").(string)),
SubscriptionName: aws.String(d.Get("name").(string)),
SourceType: aws.String(d.Get("source_type").(string)),
}

if v, ok := d.GetOk("event_categories"); ok {
request.EventCategories = expandStringList(v.(*schema.Set).List())
}

log.Println("[DEBUG] DMS update event subscription:", request)

_, err := conn.ModifyEventSubscription(request)

if err != nil {
return fmt.Errorf("Error updating DMS event subscription: %s", err)
}

stateConf := &resource.StateChangeConf{
Pending: []string{"modifying"},
Target: []string{"active"},
Refresh: resourceAwsDmsEventSubscriptionStateRefreshFunc(conn, d.Id()),
Timeout: d.Timeout(schema.TimeoutCreate),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While in the Update function, this should use the update timeout:

Suggested change
Timeout: d.Timeout(schema.TimeoutCreate),
Timeout: d.Timeout(schema.TimeoutUpdate),

MinTimeout: 10 * time.Second,
Delay: 10 * time.Second, // Wait 30 secs before starting
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Comment does not match behavior

Suggested change
Delay: 10 * time.Second, // Wait 30 secs before starting
Delay: 10 * time.Second,

}

// Wait, catching any errors
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("error waiting for DMS event subscription (%s) modification: %s", d.Id(), err)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tagging updates can be covered via:

Suggested change
if d.HasChange("tags") {
o, n := d.GetChange("tags")
if err := keyvaluetags.DatabasemigrationserviceUpdateTags(conn, d.Get("arn").(string), o, n); err != nil {
return fmt.Errorf("error updating DMS Event Subscription (%s) tags: %s", arn, err)
}
}

return resourceAwsDmsEventSubscriptionRead(d, meta)
}

func resourceAwsDmsEventSubscriptionRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn

request := &dms.DescribeEventSubscriptionsInput{
SubscriptionName: aws.String(d.Id()),
}

log.Println("[DEBUG] DMS read event subscription:", request)

response, err := conn.DescribeEventSubscriptions(request)

if isAWSErr(err, dms.ErrCodeResourceNotFoundFault, "") {
log.Printf("[WARN] DMS event subscription (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

if err != nil {
return fmt.Errorf("Error reading DMS event subscription: %s", err)
}

if response == nil || response.EventSubscriptionsList == nil || len(response.EventSubscriptionsList) == 0 {
log.Printf("[WARN] DMS event subscription (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

subscription := response.EventSubscriptionsList[0]

d.Set("enabled", subscription.Enabled)
d.Set("sns_topic_arn", subscription.SnsTopicArn)
d.Set("source_type", subscription.SourceType)
d.Set("name", d.Id())
d.Set("event_categories", flattenStringList(subscription.EventCategoriesList))
d.Set("source_ids", flattenStringList(subscription.SourceIdsList))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tags should be refreshed from the API into the Terraform state for drift detection. Since the DescribeEventSubscriptions API response does not include them, we can use the keyvaluetags package functionality to fetch them. To do so though, we need the ARN of the event subscription. The DMS User Guide section on ARNs does not explicitly list the event subscription ARN format, but I would try es: as the resource prefix since that is what RDS uses.

In the schema, add:

"arn": {
	Type:     schema.TypeString,
	Computed: true,
},

Then here:

Suggested change
arn := arn.ARN{
Partition: meta.(*AWSClient).partition,
Service: "dms",
Region: meta.(*AWSClient).region,
AccountID: meta.(*AWSClient).accountid,
Resource: fmt.Sprintf("es:%s", d.Id()),
}.String()
d.Set("arn", arn)
tags, err := keyvaluetags.DatabasemigrationserviceListTags(conn, arn)
if err != nil {
return fmt.Errorf("error listing tags for DMS Event Subscription (%s): %s", arn, err)
}
if err := d.Set("tags", tags.IgnoreAws().Map()); err != nil {
return fmt.Errorf("error setting tags: %s", err)
}

return nil
}

func resourceAwsDmsEventSubscriptionDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dmsconn

request := &dms.DeleteEventSubscriptionInput{
SubscriptionName: aws.String(d.Get("name").(string)),
}

log.Println("[DEBUG] DMS event subscription delete:", request)

_, err := conn.DeleteEventSubscription(request)

if err != nil {
return fmt.Errorf("Error deleting DMS event subscription: %s", err)
}

stateConf := &resource.StateChangeConf{
Pending: []string{"deleting"},
Target: []string{},
Refresh: resourceAwsDmsEventSubscriptionStateRefreshFunc(conn, d.Id()),
Timeout: d.Timeout(schema.TimeoutCreate),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While in the Delete function, this should use the delete timeout:

Suggested change
Timeout: d.Timeout(schema.TimeoutCreate),
Timeout: d.Timeout(schema.TimeoutDelete),

MinTimeout: 10 * time.Second,
Delay: 10 * time.Second,
}

// Wait, catching any errors
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("error waiting for DMS event subscription (%s) deletion: %s", d.Id(), err)
}

return nil
}

func resourceAwsDmsEventSubscriptionStateRefreshFunc(conn *dms.DatabaseMigrationService, name string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
v, err := conn.DescribeEventSubscriptions(&dms.DescribeEventSubscriptionsInput{
SubscriptionName: aws.String(name),
})

if isAWSErr(err, dms.ErrCodeResourceNotFoundFault, "") {
return nil, "", nil
}

if err != nil {
return nil, "", err
}

if v == nil || len(v.EventSubscriptionsList) == 0 || v.EventSubscriptionsList[0] == nil {
return nil, "", nil
}

return v, aws.StringValue(v.EventSubscriptionsList[0].Status), nil
}
}
Loading