Merge pull request #11940 from bodgit/log_destination

Add support for Amazon CloudWatch Logs PutDestination/PutDestinationPolicy
This commit is contained in:
Jake Champlin 2017-02-21 08:50:03 -05:00 committed by GitHub
commit 85fdca8cbb
11 changed files with 749 additions and 1 deletions

View File

@ -0,0 +1,31 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSCloudwatchLogDestinationPolicy_importBasic(t *testing.T) {
resourceName := "aws_cloudwatch_log_destination_policy.test"
rstring := acctest.RandString(5)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudwatchLogDestinationPolicyDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSCloudwatchLogDestinationPolicyConfig(rstring),
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -0,0 +1,31 @@
package aws
import (
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccAWSCloudwatchLogDestination_importBasic(t *testing.T) {
resourceName := "aws_cloudwatch_log_destination.test"
rstring := acctest.RandString(5)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudwatchLogDestinationDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSCloudwatchLogDestinationConfig(rstring),
},
resource.TestStep{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

View File

@ -232,6 +232,8 @@ func Provider() terraform.ResourceProvider {
"aws_cloudtrail": resourceAwsCloudTrail(),
"aws_cloudwatch_event_rule": resourceAwsCloudWatchEventRule(),
"aws_cloudwatch_event_target": resourceAwsCloudWatchEventTarget(),
"aws_cloudwatch_log_destination": resourceAwsCloudWatchLogDestination(),
"aws_cloudwatch_log_destination_policy": resourceAwsCloudWatchLogDestinationPolicy(),
"aws_cloudwatch_log_group": resourceAwsCloudWatchLogGroup(),
"aws_cloudwatch_log_metric_filter": resourceAwsCloudWatchLogMetricFilter(),
"aws_cloudwatch_log_stream": resourceAwsCloudWatchLogStream(),

View File

@ -0,0 +1,150 @@
package aws
import (
"fmt"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsCloudWatchLogDestination() *schema.Resource {
return &schema.Resource{
Create: resourceAwsCloudWatchLogDestinationPut,
Update: resourceAwsCloudWatchLogDestinationPut,
Read: resourceAwsCloudWatchLogDestinationRead,
Delete: resourceAwsCloudWatchLogDestinationDelete,
Importer: &schema.ResourceImporter{
State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
d.Set("name", d.Id())
return []*schema.ResourceData{d}, nil
},
},
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"role_arn": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"target_arn": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"arn": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
func resourceAwsCloudWatchLogDestinationPut(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cloudwatchlogsconn
name := d.Get("name").(string)
role_arn := d.Get("role_arn").(string)
target_arn := d.Get("target_arn").(string)
params := &cloudwatchlogs.PutDestinationInput{
DestinationName: aws.String(name),
RoleArn: aws.String(role_arn),
TargetArn: aws.String(target_arn),
}
return resource.Retry(3*time.Minute, func() *resource.RetryError {
resp, err := conn.PutDestination(params)
if err == nil {
d.SetId(name)
d.Set("arn", *resp.Destination.Arn)
}
awsErr, ok := err.(awserr.Error)
if !ok {
return resource.RetryableError(err)
}
if awsErr.Code() == "InvalidParameterException" {
if strings.Contains(awsErr.Message(), "Could not deliver test message to specified") {
return resource.RetryableError(err)
}
return resource.NonRetryableError(err)
}
return resource.NonRetryableError(err)
})
}
func resourceAwsCloudWatchLogDestinationRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cloudwatchlogsconn
name := d.Get("name").(string)
destination, exists, err := lookupCloudWatchLogDestination(conn, name, nil)
if err != nil {
return err
}
if !exists {
d.SetId("")
return nil
}
d.SetId(name)
d.Set("arn", destination.Arn)
d.Set("role_arn", destination.RoleArn)
d.Set("target_arn", destination.TargetArn)
return nil
}
func resourceAwsCloudWatchLogDestinationDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cloudwatchlogsconn
name := d.Get("name").(string)
params := &cloudwatchlogs.DeleteDestinationInput{
DestinationName: aws.String(name),
}
_, err := conn.DeleteDestination(params)
if err != nil {
return fmt.Errorf("Error deleting Destination with name %s", name)
}
d.SetId("")
return nil
}
func lookupCloudWatchLogDestination(conn *cloudwatchlogs.CloudWatchLogs,
name string, nextToken *string) (*cloudwatchlogs.Destination, bool, error) {
input := &cloudwatchlogs.DescribeDestinationsInput{
DestinationNamePrefix: aws.String(name),
NextToken: nextToken,
}
resp, err := conn.DescribeDestinations(input)
if err != nil {
return nil, true, err
}
for _, destination := range resp.Destinations {
if *destination.DestinationName == name {
return destination, true, nil
}
}
if resp.NextToken != nil {
return lookupCloudWatchLogDestination(conn, name, resp.NextToken)
}
return nil, false, nil
}

View File

@ -0,0 +1,88 @@
package aws
import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
)
func resourceAwsCloudWatchLogDestinationPolicy() *schema.Resource {
return &schema.Resource{
Create: resourceAwsCloudWatchLogDestinationPolicyPut,
Update: resourceAwsCloudWatchLogDestinationPolicyPut,
Read: resourceAwsCloudWatchLogDestinationPolicyRead,
Delete: resourceAwsCloudWatchLogDestinationPolicyDelete,
Importer: &schema.ResourceImporter{
State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
d.Set("destination_name", d.Id())
return []*schema.ResourceData{d}, nil
},
},
Schema: map[string]*schema.Schema{
"destination_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"access_policy": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
},
}
}
func resourceAwsCloudWatchLogDestinationPolicyPut(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cloudwatchlogsconn
destination_name := d.Get("destination_name").(string)
access_policy := d.Get("access_policy").(string)
params := &cloudwatchlogs.PutDestinationPolicyInput{
DestinationName: aws.String(destination_name),
AccessPolicy: aws.String(access_policy),
}
_, err := conn.PutDestinationPolicy(params)
if err != nil {
return fmt.Errorf("Error creating DestinationPolicy with destination_name %s: %#v", destination_name, err)
}
d.SetId(destination_name)
return resourceAwsCloudWatchLogDestinationPolicyRead(d, meta)
}
func resourceAwsCloudWatchLogDestinationPolicyRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cloudwatchlogsconn
destination_name := d.Get("destination_name").(string)
destination, exists, err := lookupCloudWatchLogDestination(conn, destination_name, nil)
if err != nil {
return err
}
if !exists {
d.SetId("")
return nil
}
if destination.AccessPolicy != nil {
d.SetId(destination_name)
d.Set("access_policy", *destination.AccessPolicy)
} else {
d.SetId("")
}
return nil
}
func resourceAwsCloudWatchLogDestinationPolicyDelete(d *schema.ResourceData, meta interface{}) error {
d.SetId("")
return nil
}

View File

@ -0,0 +1,164 @@
package aws
import (
"fmt"
"testing"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSCloudwatchLogDestinationPolicy_basic(t *testing.T) {
var destination cloudwatchlogs.Destination
rstring := acctest.RandString(5)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudwatchLogDestinationPolicyDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSCloudwatchLogDestinationPolicyConfig(rstring),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSCloudwatchLogDestinationPolicyExists("aws_cloudwatch_log_destination_policy.test", &destination),
),
},
},
})
}
func testAccCheckAWSCloudwatchLogDestinationPolicyDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).cloudwatchlogsconn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_cloudwatch_log_destination_policy" {
continue
}
_, exists, err := lookupCloudWatchLogDestination(conn, rs.Primary.ID, nil)
if err != nil {
return nil
}
if exists {
return fmt.Errorf("Bad: Destination Policy still exists: %q", rs.Primary.ID)
}
}
return nil
}
func testAccCheckAWSCloudwatchLogDestinationPolicyExists(n string, d *cloudwatchlogs.Destination) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
conn := testAccProvider.Meta().(*AWSClient).cloudwatchlogsconn
destination, exists, err := lookupCloudWatchLogDestination(conn, rs.Primary.ID, nil)
if err != nil {
return err
}
if !exists || destination.AccessPolicy == nil {
return fmt.Errorf("Bad: Destination Policy %q does not exist", rs.Primary.ID)
}
*d = *destination
return nil
}
}
func testAccAWSCloudwatchLogDestinationPolicyConfig(rstring string) string {
return fmt.Sprintf(`
resource "aws_kinesis_stream" "test" {
name = "RootAccess_%s"
shard_count = 1
}
data "aws_region" "current" {
current = true
}
data "aws_iam_policy_document" "role" {
statement {
effect = "Allow"
principals = {
type = "Service"
identifiers = [
"logs.${data.aws_region.current.name}.amazonaws.com"
]
}
actions = [
"sts:AssumeRole",
]
}
}
resource "aws_iam_role" "test" {
name = "CWLtoKinesisRole_%s"
assume_role_policy = "${data.aws_iam_policy_document.role.json}"
}
data "aws_iam_policy_document" "policy" {
statement {
effect = "Allow"
actions = [
"kinesis:PutRecord",
]
resources = [
"${aws_kinesis_stream.test.arn}"
]
}
statement {
effect = "Allow"
actions = [
"iam:PassRole"
]
resources = [
"${aws_iam_role.test.arn}"
]
}
}
resource "aws_iam_role_policy" "test" {
name = "Permissions-Policy-For-CWL_%s"
role = "${aws_iam_role.test.id}"
policy = "${data.aws_iam_policy_document.policy.json}"
}
resource "aws_cloudwatch_log_destination" "test" {
name = "testDestination_%s"
target_arn = "${aws_kinesis_stream.test.arn}"
role_arn = "${aws_iam_role.test.arn}"
depends_on = ["aws_iam_role_policy.test"]
}
data "aws_iam_policy_document" "access" {
statement {
effect = "Allow"
principals = {
type = "AWS"
identifiers = [
"000000000000"
]
}
actions = [
"logs:PutSubscriptionFilter"
]
resources = [
"${aws_cloudwatch_log_destination.test.arn}"
]
}
}
resource "aws_cloudwatch_log_destination_policy" "test" {
destination_name = "${aws_cloudwatch_log_destination.test.name}"
access_policy = "${data.aws_iam_policy_document.access.json}"
}
`, rstring, rstring, rstring, rstring)
}

View File

@ -0,0 +1,164 @@
package aws
import (
"fmt"
"testing"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSCloudwatchLogDestination_basic(t *testing.T) {
var destination cloudwatchlogs.Destination
rstring := acctest.RandString(5)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudwatchLogDestinationDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSCloudwatchLogDestinationConfig(rstring),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSCloudwatchLogDestinationExists("aws_cloudwatch_log_destination.test", &destination),
),
},
},
})
}
func testAccCheckAWSCloudwatchLogDestinationDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).cloudwatchlogsconn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_cloudwatch_log_destination" {
continue
}
_, exists, err := lookupCloudWatchLogDestination(conn, rs.Primary.ID, nil)
if err != nil {
return nil
}
if exists {
return fmt.Errorf("Bad: Destination still exists: %q", rs.Primary.ID)
}
}
return nil
}
func testAccCheckAWSCloudwatchLogDestinationExists(n string, d *cloudwatchlogs.Destination) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
conn := testAccProvider.Meta().(*AWSClient).cloudwatchlogsconn
destination, exists, err := lookupCloudWatchLogDestination(conn, rs.Primary.ID, nil)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("Bad: Destination %q does not exist", rs.Primary.ID)
}
*d = *destination
return nil
}
}
func testAccAWSCloudwatchLogDestinationConfig(rstring string) string {
return fmt.Sprintf(`
resource "aws_kinesis_stream" "test" {
name = "RootAccess_%s"
shard_count = 1
}
data "aws_region" "current" {
current = true
}
data "aws_iam_policy_document" "role" {
statement {
effect = "Allow"
principals = {
type = "Service"
identifiers = [
"logs.${data.aws_region.current.name}.amazonaws.com"
]
}
actions = [
"sts:AssumeRole",
]
}
}
resource "aws_iam_role" "test" {
name = "CWLtoKinesisRole_%s"
assume_role_policy = "${data.aws_iam_policy_document.role.json}"
}
data "aws_iam_policy_document" "policy" {
statement {
effect = "Allow"
actions = [
"kinesis:PutRecord",
]
resources = [
"${aws_kinesis_stream.test.arn}"
]
}
statement {
effect = "Allow"
actions = [
"iam:PassRole"
]
resources = [
"${aws_iam_role.test.arn}"
]
}
}
resource "aws_iam_role_policy" "test" {
name = "Permissions-Policy-For-CWL_%s"
role = "${aws_iam_role.test.id}"
policy = "${data.aws_iam_policy_document.policy.json}"
}
resource "aws_cloudwatch_log_destination" "test" {
name = "testDestination_%s"
target_arn = "${aws_kinesis_stream.test.arn}"
role_arn = "${aws_iam_role.test.arn}"
depends_on = ["aws_iam_role_policy.test"]
}
data "aws_iam_policy_document" "access" {
statement {
effect = "Allow"
principals = {
type = "AWS"
identifiers = [
"000000000000"
]
}
actions = [
"logs:PutSubscriptionFilter"
]
resources = [
"${aws_cloudwatch_log_destination.test.arn}"
]
}
}
resource "aws_cloudwatch_log_destination_policy" "test" {
destination_name = "${aws_cloudwatch_log_destination.test.name}"
access_policy = "${data.aws_iam_policy_document.access.json}"
}
`, rstring, rstring, rstring, rstring)
}

View File

@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"log"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
@ -56,7 +57,7 @@ func resourceAwsCloudwatchLogSubscriptionFilterCreate(d *schema.ResourceData, me
params := getAwsCloudWatchLogsSubscriptionFilterInput(d)
log.Printf("[DEBUG] Creating SubscriptionFilter %#v", params)
return resource.Retry(30*time.Second, func() *resource.RetryError {
return resource.Retry(3*time.Minute, func() *resource.RetryError {
_, err := conn.PutSubscriptionFilter(&params)
if err == nil {
@ -71,6 +72,9 @@ func resourceAwsCloudwatchLogSubscriptionFilterCreate(d *schema.ResourceData, me
if awsErr.Code() == "InvalidParameterException" {
log.Printf("[DEBUG] Caught message: %q, code: %q: Retrying", awsErr.Message(), awsErr.Code())
if strings.Contains(awsErr.Message(), "Could not deliver test message to specified") {
return resource.RetryableError(err)
}
resource.NonRetryableError(err)
}

View File

@ -0,0 +1,43 @@
---
layout: "aws"
page_title: "AWS: aws_cloudwatch_log_destination"
sidebar_current: "docs-aws-resource-cloudwatch-log-destination"
description: |-
Provides a CloudWatch Logs destination.
---
# aws\_cloudwatch\_log\_destination
Provides a CloudWatch Logs destination resource.
## Example Usage
```
resource "aws_cloudwatch_log_destination" "test_destination" {
name = "test_destination"
role_arn = "${aws_iam_role.iam_for_cloudwatch.arn}"
target_arn = "${aws_kinesis_stream.kinesis_for_cloudwatch.arn}"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) A name for the log destination
* `role_arn` - (Required) The ARN of an IAM role that grants Amazon CloudWatch Logs permissions to put data into the target
* `target_arn` - (Required) The ARN of the target Amazon Kinesis stream or Amazon Lambda resource for the destination
## Attributes Reference
The following attributes are exported:
* `arn` - The Amazon Resource Name (ARN) specifying the log destination.
## Import
CloudWatch Logs destinations can be imported using the `name`, e.g.
```
$ terraform import aws_cloudwatch_log_destination.test_destination test_destination
```

View File

@ -0,0 +1,63 @@
---
layout: "aws"
page_title: "AWS: aws_cloudwatch_log_destination_policy"
sidebar_current: "docs-aws-resource-cloudwatch-log-destination-policy"
description: |-
Provides a CloudWatch Logs destination policy.
---
# aws\_cloudwatch\_log\_destination\_policy
Provides a CloudWatch Logs destination policy resource.
## Example Usage
```
resource "aws_cloudwatch_log_destination" "test_destination" {
name = "test_destination"
role_arn = "${aws_iam_role.iam_for_cloudwatch.arn}"
target_arn = "${aws_kinesis_stream.kinesis_for_cloudwatch.arn}"
}
data "aws_iam_policy_document" "test_destination_policy" {
statement {
effect = "Allow"
principals = {
type = "AWS"
identifiers = [
"123456789012",
]
}
actions = [
"logs:PutSubscriptionFilter",
]
resources = [
"${aws_cloudwatch_log_destination.test_destination.arn}",
]
}
}
resource "aws_cloudwatch_log_destination_policy" "test_destination_policy" {
destination_name = "${aws_cloudwatch_log_destination.test_destination.name}"
access_policy = "${data.aws_iam_policy_document.test_destination_policy.json}"
}
```
## Argument Reference
The following arguments are supported:
* `destination_name` - (Required) A name for the subscription filter
* `access_policy` - (Required) The policy document. This is a JSON formatted string.
## Import
CloudWatch Logs destination policies can be imported using the `destination_name`, e.g.
```
$ terraform import aws_cloudwatch_log_destination_policy.test_destination_policy test_destination
```

View File

@ -231,6 +231,14 @@
<a href="/docs/providers/aws/r/cloudwatch_event_target.html">aws_cloudwatch_event_target</a>
</li>
<li<%= sidebar_current("docs-aws-resource-cloudwatch-log-destination") %>>
<a href="/docs/providers/aws/r/cloudwatch_log_destination.html">aws_cloudwatch_log_destination</a>
</li>
<li<%= sidebar_current("docs-aws-resource-cloudwatch-log-destination-policy") %>>
<a href="/docs/providers/aws/r/cloudwatch_log_destination_policy.html">aws_cloudwatch_log_destination_policy</a>
</li>
<li<%= sidebar_current("docs-aws-resource-cloudwatch-log-group") %>>
<a href="/docs/providers/aws/r/cloudwatch_log_group.html">aws_cloudwatch_log_group</a>
</li>