diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 6730c47ee..3b4771813 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -136,6 +136,7 @@ func Provider() terraform.ResourceProvider { "aws_cloudwatch_event_target": resourceAwsCloudWatchEventTarget(), "aws_cloudwatch_log_group": resourceAwsCloudWatchLogGroup(), "aws_cloudwatch_log_metric_filter": resourceAwsCloudWatchLogMetricFilter(), + "aws_cloudwatch_log_subscription_filter": resourceAwsCloudwatchLogSubscriptionFilter(), "aws_autoscaling_lifecycle_hook": resourceAwsAutoscalingLifecycleHook(), "aws_cloudwatch_metric_alarm": resourceAwsCloudWatchMetricAlarm(), "aws_codedeploy_app": resourceAwsCodeDeployApp(), diff --git a/builtin/providers/aws/resource_aws_cloudwatch_log_subscription_filter.go b/builtin/providers/aws/resource_aws_cloudwatch_log_subscription_filter.go new file mode 100644 index 000000000..479a78dba --- /dev/null +++ b/builtin/providers/aws/resource_aws_cloudwatch_log_subscription_filter.go @@ -0,0 +1,171 @@ +package aws + +import ( + "bytes" + "fmt" + "log" + "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/hashcode" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsCloudwatchLogSubscriptionFilter() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsCloudwatchLogSubscriptionFilterCreate, + Read: resourceAwsCloudwatchLogSubscriptionFilterRead, + Update: resourceAwsCloudwatchLogSubscriptionFilterUpdate, + Delete: resourceAwsCloudwatchLogSubscriptionFilterDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "destination_arn": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "filter_pattern": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + "log_group_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "role_arn": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceAwsCloudwatchLogSubscriptionFilterCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloudwatchlogsconn + params := getAwsCloudWatchLogsSubscriptionFilterInput(d) + log.Printf("[DEBUG] Creating SubscriptionFilter %#v", params) + + return resource.Retry(30*time.Second, func() *resource.RetryError { + _, err := conn.PutSubscriptionFilter(¶ms) + + if err == nil { + d.SetId(cloudwatchLogsSubscriptionFilterId(d.Get("log_group_name").(string))) + log.Printf("[DEBUG] Cloudwatch logs subscription %q created", d.Id()) + } + + awsErr, ok := err.(awserr.Error) + if !ok { + return resource.RetryableError(err) + } + + if awsErr.Code() == "InvalidParameterException" { + log.Printf("[DEBUG] Caught message: %q, code: %q: Retrying", awsErr.Message(), awsErr.Code()) + resource.NonRetryableError(err) + } + + return resource.NonRetryableError(err) + }) +} + +func resourceAwsCloudwatchLogSubscriptionFilterUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloudwatchlogsconn + + params := getAwsCloudWatchLogsSubscriptionFilterInput(d) + + log.Printf("[DEBUG] Update SubscriptionFilter %#v", params) + _, err := conn.PutSubscriptionFilter(¶ms) + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + return fmt.Errorf("[WARN] Error updating SubscriptionFilter (%s) for LogGroup (%s), message: \"%s\", code: \"%s\"", + d.Get("name").(string), d.Get("log_group_name").(string), awsErr.Message(), awsErr.Code()) + } + return err + } + + d.SetId(cloudwatchLogsSubscriptionFilterId(d.Get("log_group_name").(string))) + return resourceAwsCloudwatchLogSubscriptionFilterRead(d, meta) +} + +func getAwsCloudWatchLogsSubscriptionFilterInput(d *schema.ResourceData) cloudwatchlogs.PutSubscriptionFilterInput { + name := d.Get("name").(string) + destination_arn := d.Get("destination_arn").(string) + filter_pattern := d.Get("filter_pattern").(string) + log_group_name := d.Get("log_group_name").(string) + + params := cloudwatchlogs.PutSubscriptionFilterInput{ + FilterName: aws.String(name), + DestinationArn: aws.String(destination_arn), + FilterPattern: aws.String(filter_pattern), + LogGroupName: aws.String(log_group_name), + } + + if _, ok := d.GetOk("role_arn"); ok { + params.RoleArn = aws.String(d.Get("role_arn").(string)) + } + + return params +} + +func resourceAwsCloudwatchLogSubscriptionFilterRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloudwatchlogsconn + + log_group_name := d.Get("log_group_name").(string) + name := d.Get("name").(string) // "name" is a required field in the schema + + req := &cloudwatchlogs.DescribeSubscriptionFiltersInput{ + LogGroupName: aws.String(log_group_name), + FilterNamePrefix: aws.String(name), + } + + resp, err := conn.DescribeSubscriptionFilters(req) + if err != nil { + return fmt.Errorf("Error reading SubscriptionFilters for log group %s with name prefix %s: %#v", log_group_name, d.Get("name").(string), err) + } + + for _, subscriptionFilter := range resp.SubscriptionFilters { + if *subscriptionFilter.LogGroupName == log_group_name { + d.SetId(cloudwatchLogsSubscriptionFilterId(log_group_name)) + return nil // OK, matching subscription filter found + } + } + + return fmt.Errorf("Subscription filter for log group %s with name prefix %s not found!", log_group_name, d.Get("name").(string)) +} + +func resourceAwsCloudwatchLogSubscriptionFilterDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloudwatchlogsconn + log.Printf("[INFO] Deleting CloudWatch Log Group Subscription: %s", d.Id()) + log_group_name := d.Get("log_group_name").(string) + name := d.Get("name").(string) + + params := &cloudwatchlogs.DeleteSubscriptionFilterInput{ + FilterName: aws.String(name), // Required + LogGroupName: aws.String(log_group_name), // Required + } + _, err := conn.DeleteSubscriptionFilter(params) + if err != nil { + return fmt.Errorf( + "Error deleting Subscription Filter from log group: %s with name filter name %s", log_group_name, name) + } + d.SetId("") + return nil +} + +func cloudwatchLogsSubscriptionFilterId(log_group_name string) string { + var buf bytes.Buffer + + buf.WriteString(fmt.Sprintf("%s-", log_group_name)) // only one filter allowed per log_group_name at the moment + + return fmt.Sprintf("cwlsf-%d", hashcode.String(buf.String())) +} diff --git a/builtin/providers/aws/resource_aws_cloudwatch_log_subscription_filter_test.go b/builtin/providers/aws/resource_aws_cloudwatch_log_subscription_filter_test.go new file mode 100644 index 000000000..bdc6caf5b --- /dev/null +++ b/builtin/providers/aws/resource_aws_cloudwatch_log_subscription_filter_test.go @@ -0,0 +1,161 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/lambda" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSCloudwatchLogSubscriptionFilter_basic(t *testing.T) { + var conf lambda.GetFunctionOutput + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudwatchLogSubscriptionFilterDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSCloudwatchLogSubscriptionFilterConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsCloudwatchLogSubscriptionFilterExists("aws_cloudwatch_log_subscription_filter.test_lambdafunction_logfilter", &conf), + testAccCheckAWSCloudwatchLogSubscriptionFilterAttributes(&conf), + ), + }, + }, + }) +} + +func testAccCheckCloudwatchLogSubscriptionFilterDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).lambdaconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_cloudwatch_log_subscription_filter" { + continue + } + + _, err := conn.GetFunction(&lambda.GetFunctionInput{ + FunctionName: aws.String(rs.Primary.ID), + }) + + if err == nil { + return fmt.Errorf("Lambda Function still exists") + } + + } + + return nil + +} + +func testAccCheckAwsCloudwatchLogSubscriptionFilterExists(n string, function *lambda.GetFunctionOutput) resource.TestCheckFunc { + // Wait for IAM role + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Lambda function not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("Lambda function ID not set") + } + + conn := testAccProvider.Meta().(*AWSClient).lambdaconn + + params := &lambda.GetFunctionInput{ + FunctionName: aws.String("example_lambda_name"), + } + + getFunction, err := conn.GetFunction(params) + if err != nil { + return err + } + + *function = *getFunction + + return nil + } +} + +func testAccCheckAWSCloudwatchLogSubscriptionFilterAttributes(function *lambda.GetFunctionOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + c := function.Configuration + const expectedName = "example_lambda_name" + if *c.FunctionName != expectedName { + return fmt.Errorf("Expected function name %s, got %s", expectedName, *c.FunctionName) + } + + if *c.FunctionArn == "" { + return fmt.Errorf("Could not read Lambda Function's ARN") + } + + return nil + } +} + +const testAccAWSCloudwatchLogSubscriptionFilterConfig = ` +resource "aws_cloudwatch_log_subscription_filter" "test_lambdafunction_logfilter" { + name = "test_lambdafunction_logfilter" + log_group_name = "example_lambda_name" + filter_pattern = "logtype test" + destination_arn = "${aws_lambda_function.test_lambdafunction.arn}" +} +resource "aws_lambda_function" "test_lambdafunction" { + filename = "test-fixtures/lambdatest.zip" + function_name = "example_lambda_name" + role = "${aws_iam_role.iam_for_lambda.arn}" + handler = "exports.handler" +} +resource "aws_cloudwatch_log_group" "logs" { + name = "example_lambda_name" + retention_in_days = 1 +} +resource "aws_lambda_permission" "allow_cloudwatch_logs" { + statement_id = "AllowExecutionFromCloudWatchLogs" + action = "lambda:*" + function_name = "${aws_lambda_function.test_lambdafunction.arn}" + principal = "logs.eu-west-1.amazonaws.com" +} +resource "aws_iam_role" "iam_for_lambda" { + name = "test_lambdafuntion_iam_role" + assume_role_policy = <aws_cloudwatch_log_group + > + aws_cloudwatch_log_subscription_filter + + > aws_cloudwatch_log_metric_filter