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

New Resource: aws_ssm_resource_data_sync #1895

Merged
merged 6 commits into from
Nov 10, 2017
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -450,6 +450,7 @@ func Provider() terraform.ResourceProvider {
"aws_ssm_patch_baseline": resourceAwsSsmPatchBaseline(),
"aws_ssm_patch_group": resourceAwsSsmPatchGroup(),
"aws_ssm_parameter": resourceAwsSsmParameter(),
"aws_ssm_resource_data_sync": resourceAwsSsmResourceDataSync(),
"aws_spot_datafeed_subscription": resourceAwsSpotDataFeedSubscription(),
"aws_spot_instance_request": resourceAwsSpotInstanceRequest(),
"aws_spot_fleet_request": resourceAwsSpotFleetRequest(),
Expand Down
159 changes: 159 additions & 0 deletions aws/resource_aws_ssm_resource_data_sync.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package aws

import (
"fmt"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ssm"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsSsmResourceDataSync() *schema.Resource {
return &schema.Resource{
Create: resourceAwsSsmResourceDataSyncCreate,
Read: resourceAwsSsmResourceDataSyncRead,
Delete: resourceAwsSsmResourceDataSyncDelete,

Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"s3_destination": {
Type: schema.TypeList,
Required: true,
ForceNew: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"kms_key_arn": {
Type: schema.TypeString,
Optional: true,
},
"bucket_name": {
Type: schema.TypeString,
Required: true,
},
"prefix": {
Type: schema.TypeString,
Optional: true,
},
"region": {
Type: schema.TypeString,
Required: true,
},
"sync_format": {
Type: schema.TypeString,
Optional: true,
Default: ssm.ResourceDataSyncS3FormatJsonSerDe,
},
},
},
},
},
}
}

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

input := &ssm.CreateResourceDataSyncInput{
S3Destination: expandSsmResourceDataSyncS3Destination(d),
SyncName: aws.String(d.Get("name").(string)),
}

_, err := conn.CreateResourceDataSync(input)
if err != nil {
return err
}
d.SetId(d.Get("name").(string))
return resourceAwsSsmResourceDataSyncRead(d, meta)
}

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

syncItem, err := findResourceDataSyncItem(conn, d.Get("name").(string))
if err != nil {
return err
}
Copy link
Member

Choose a reason for hiding this comment

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

Do you mind moving this for loop into a separate function, e.g. findResourceDataSync(conn *ssm.SSM, name string) *ssm.ResourceDataSyncItem?

if syncItem == nil {
d.SetId("")
return nil
}
Copy link
Member

Choose a reason for hiding this comment

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

There's a few things missing here - we need to re-set all the fields here based on what came from the API, e.g.

d.Set("s3_destination", flattenS3Destination(ds.S3Destination))

where the flattener will turn the SDK object into []interface{}

and secondly if there's no sync found we should remove it from state by calling d.SetId("").

d.Set("s3_destination", flattenSsmResourceDataSyncS3Destination(syncItem.S3Destination))
return nil
}

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

input := &ssm.DeleteResourceDataSyncInput{
SyncName: aws.String(d.Get("name").(string)),
}

_, err := conn.DeleteResourceDataSync(input)
if err != nil {
if isAWSErr(err, ssm.ErrCodeResourceDataSyncNotFoundException, "") {
return nil
}
return err
}
Copy link
Member

Choose a reason for hiding this comment

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

All of the above can be IMO simplified to something like this:

if isAWSErr(err, ssm.ErrCodeResourceDataSyncNotFoundException, "") {
    return nil
}
return err

return nil
}

func findResourceDataSyncItem(conn *ssm.SSM, name string) (*ssm.ResourceDataSyncItem, error) {
nextToken := ""
for {
input := &ssm.ListResourceDataSyncInput{}
if nextToken != "" {
input.NextToken = aws.String(nextToken)
}
resp, err := conn.ListResourceDataSync(input)
if err != nil {
return nil, err
}
for _, v := range resp.ResourceDataSyncItems {
if *v.SyncName == name {
return v, nil
}
}
if resp.NextToken == nil {
break
}
nextToken = *resp.NextToken
}
return nil, nil
}

func flattenSsmResourceDataSyncS3Destination(dest *ssm.ResourceDataSyncS3Destination) []interface{} {
result := make(map[string]interface{})
result["bucket_name"] = *dest.BucketName
result["region"] = *dest.Region
result["sync_format"] = *dest.SyncFormat
if dest.AWSKMSKeyARN != nil {
result["kms_key_arn"] = *dest.AWSKMSKeyARN
}
if dest.Prefix != nil {
result["prefix"] = *dest.Prefix
}
return []interface{}{result}
}

func expandSsmResourceDataSyncS3Destination(d *schema.ResourceData) *ssm.ResourceDataSyncS3Destination {
raw := d.Get("s3_destination").([]interface{})[0].(map[string]interface{})
s3dest := &ssm.ResourceDataSyncS3Destination{
BucketName: aws.String(raw["bucket_name"].(string)),
Region: aws.String(raw["region"].(string)),
SyncFormat: aws.String(raw["sync_format"].(string)),
}
if v, ok := raw["kms_key_arn"].(string); ok && v != "" {
s3dest.AWSKMSKeyARN = aws.String(v)
}
if v, ok := raw["prefix"].(string); ok && v != "" {
s3dest.Prefix = aws.String(v)
}
return s3dest
}
109 changes: 109 additions & 0 deletions aws/resource_aws_ssm_resource_data_sync_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package aws

import (
"fmt"
"log"
"testing"

"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSSsmResourceDataSync_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSsmResourceDataSyncDestroy,
Steps: []resource.TestStep{
{
Config: testAccSsmResourceDataSyncConfig(acctest.RandInt(), acctest.RandString(5)),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSsmResourceDataSyncExists("aws_ssm_resource_data_sync.foo"),
),
},
},
})
}

func testAccCheckAWSSsmResourceDataSyncDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ssmconn

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_ssm_resource_data_sync" {
continue
}
syncItem, err := findResourceDataSyncItem(conn, rs.Primary.Attributes["name"])
if err != nil {
return err
}
Copy link
Member

Choose a reason for hiding this comment

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

Once you have decoupled the logic for finding the right data sync, it can also be reused here 😉

if syncItem != nil {
return fmt.Errorf("Resource Data Sync (%s) found", rs.Primary.Attributes["name"])
}
}
return nil
}

func testAccCheckAWSSsmResourceDataSyncExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
log.Println(s.RootModule().Resources)
_, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}
return nil
}
}

func testAccSsmResourceDataSyncConfig(rInt int, rName string) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "hoge" {
bucket = "tf-test-bucket-%d"
region = "us-west-2"
force_destroy = true
}

resource "aws_s3_bucket_policy" "hoge" {
bucket = "${aws_s3_bucket.hoge.bucket}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SSMBucketPermissionsCheck",
"Effect": "Allow",
"Principal": {
"Service": "ssm.amazonaws.com"
},
"Action": "s3:GetBucketAcl",
"Resource": "arn:aws:s3:::tf-test-bucket-%d"
},
{
"Sid": " SSMBucketDelivery",
"Effect": "Allow",
"Principal": {
"Service": "ssm.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": ["arn:aws:s3:::tf-test-bucket-%d/*"],
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control"
}
}
}
]
}
EOF
}

resource "aws_ssm_resource_data_sync" "foo" {
name = "tf-test-ssm-%s"
s3_destination = {
bucket_name = "${aws_s3_bucket.hoge.bucket}"
region = "${aws_s3_bucket.hoge.region}"
}
}
`, rInt, rInt, rInt, rName)
}
4 changes: 4 additions & 0 deletions website/aws.erb
Original file line number Diff line number Diff line change
Expand Up @@ -1425,6 +1425,10 @@
<a href="/docs/providers/aws/r/ssm_parameter.html">aws_ssm_parameter</a>
</li>

<li<%= sidebar_current("docs-aws-resource-ssm-resource-data-sync") %>>
<a href="/docs/providers/aws/r/ssm_resource_data_sync.html">aws_ssm_resource_data_sync</a>
</li>

</ul>
</li>

Expand Down
79 changes: 79 additions & 0 deletions website/docs/r/ssm_resource_data_sync.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
layout: "aws"
page_title: "AWS: aws_ssm_resource_data_sync"
sidebar_current: "docs-aws-resource-ssm-resource-data-sync"
description: |-
Provides a SSM resource data sync.
---

# aws_athena_database

Provides a SSM resource data sync.

## Example Usage

```hcl
resource "aws_s3_bucket" "hoge" {
bucket = "tf-test-bucket-1234"
region = "us-east-1"
}

resource "aws_s3_bucket_policy" "hoge" {
bucket = "${aws_s3_bucket.hoge.bucket}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SSMBucketPermissionsCheck",
"Effect": "Allow",
"Principal": {
"Service": "ssm.amazonaws.com"
},
"Action": "s3:GetBucketAcl",
"Resource": "arn:aws:s3:::tf-test-bucket-1234"
},
{
"Sid": " SSMBucketDelivery",
"Effect": "Allow",
"Principal": {
"Service": "ssm.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": ["arn:aws:s3:::tf-test-bucket-1234/*"],
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control"
}
}
}
]
}
EOF
}

resource "aws_ssm_resource_data_sync" "foo" {
name = "foo"
s3_destination = {
bucket_name = "${aws_s3_bucket.hoge.bucket}"
region = "${aws_s3_bucket.hoge.region}"
}
}
```

## Argument Reference

The following arguments are supported:

* `name` - (Required) Name for the configuration.
* `s3_destination` - (Required) Amazon S3 configuration details for the sync.

## s3_destination

`s3_destination` supports the following:

* `bucket_name` - (Required) Name of S3 bucket where the aggregated data is stored.
* `region` - (Required) Region with the bucket targeted by the Resource Data Sync.
* `kms_key_arn` - (Optional) ARN of an encryption key for a destination in Amazon S3.
* `prefix` - (Optional) Prefix for the bucket.
* `sync_format` - (Optional) A supported sync format. Only JsonSerDe is currently supported. Defaults to JsonSerDe.