diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 38d72b5d6..655c88fc4 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -193,6 +193,7 @@ func Provider() terraform.ResourceProvider { "aws_cloudwatch_event_target": resourceAwsCloudWatchEventTarget(), "aws_cloudwatch_log_group": resourceAwsCloudWatchLogGroup(), "aws_cloudwatch_log_metric_filter": resourceAwsCloudWatchLogMetricFilter(), + "aws_cloudwatch_log_stream": resourceAwsCloudWatchLogStream(), "aws_cloudwatch_log_subscription_filter": resourceAwsCloudwatchLogSubscriptionFilter(), "aws_autoscaling_lifecycle_hook": resourceAwsAutoscalingLifecycleHook(), "aws_cloudwatch_metric_alarm": resourceAwsCloudWatchMetricAlarm(), diff --git a/builtin/providers/aws/resource_aws_cloudwatch_log_stream.go b/builtin/providers/aws/resource_aws_cloudwatch_log_stream.go new file mode 100644 index 000000000..ba66f4843 --- /dev/null +++ b/builtin/providers/aws/resource_aws_cloudwatch_log_stream.go @@ -0,0 +1,133 @@ +package aws + +import ( + "fmt" + "log" + "regexp" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudwatchlogs" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsCloudWatchLogStream() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsCloudWatchLogStreamCreate, + Read: resourceAwsCloudWatchLogStreamRead, + Delete: resourceAwsCloudWatchLogStreamDelete, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateCloudWatchLogStreamName, + }, + + "log_group_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceAwsCloudWatchLogStreamCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloudwatchlogsconn + + log.Printf("[DEBUG] Creating CloudWatch Log Stream: %s", d.Get("name").(string)) + _, err := conn.CreateLogStream(&cloudwatchlogs.CreateLogStreamInput{ + LogGroupName: aws.String(d.Get("log_group_name").(string)), + LogStreamName: aws.String(d.Get("name").(string)), + }) + if err != nil { + return errwrap.Wrapf("Creating CloudWatch Log Stream failed: %s", err) + } + + d.SetId(d.Get("name").(string)) + + return resourceAwsCloudWatchLogStreamRead(d, meta) +} + +func resourceAwsCloudWatchLogStreamRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloudwatchlogsconn + + ls, exists, err := lookupCloudWatchLogStream(conn, d.Id(), d.Get("log_group_name").(string), nil) + if err != nil { + return err + } + + if !exists { + log.Printf("[DEBUG] CloudWatch Stream %q Not Found. Removing from state", d.Id()) + d.SetId("") + return nil + } + + d.Set("arn", ls.Arn) + d.Set("name", ls.LogStreamName) + + return nil +} + +func resourceAwsCloudWatchLogStreamDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloudwatchlogsconn + + log.Printf("[INFO] Deleting CloudWatch Log Stream: %s", d.Id()) + params := &cloudwatchlogs.DeleteLogStreamInput{ + LogGroupName: aws.String(d.Get("log_group_name").(string)), + LogStreamName: aws.String(d.Id()), + } + _, err := conn.DeleteLogStream(params) + if err != nil { + return errwrap.Wrapf("Error deleting CloudWatch Log Stream: %s", err) + } + + return nil +} + +func lookupCloudWatchLogStream(conn *cloudwatchlogs.CloudWatchLogs, + name string, logStreamName string, nextToken *string) (*cloudwatchlogs.LogStream, bool, error) { + input := &cloudwatchlogs.DescribeLogStreamsInput{ + LogStreamNamePrefix: aws.String(name), + LogGroupName: aws.String(logStreamName), + NextToken: nextToken, + } + resp, err := conn.DescribeLogStreams(input) + if err != nil { + return nil, true, err + } + + for _, ls := range resp.LogStreams { + if *ls.LogStreamName == name { + return ls, true, nil + } + } + + if resp.NextToken != nil { + return lookupCloudWatchLogStream(conn, name, logStreamName, resp.NextToken) + } + + return nil, false, nil +} + +func validateCloudWatchLogStreamName(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if regexp.MustCompile(`:`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "colons not allowed in %q:", k)) + } + if len(value) < 1 || len(value) > 512 { + errors = append(errors, fmt.Errorf( + "%q must be between 1 and 512 characters: %q", k, value)) + } + + return + +} diff --git a/builtin/providers/aws/resource_aws_cloudwatch_log_stream_test.go b/builtin/providers/aws/resource_aws_cloudwatch_log_stream_test.go new file mode 100644 index 000000000..9e39b1cd2 --- /dev/null +++ b/builtin/providers/aws/resource_aws_cloudwatch_log_stream_test.go @@ -0,0 +1,152 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "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 TestAccAWSCloudWatchLogStream_basic(t *testing.T) { + var ls cloudwatchlogs.LogStream + rName := acctest.RandString(15) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCloudWatchLogStreamDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudWatchLogStreamConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchLogStreamExists("aws_cloudwatch_log_stream.foobar", &ls), + ), + }, + }, + }) +} + +func TestAccAWSCloudWatchLogStream_disappears(t *testing.T) { + var ls cloudwatchlogs.LogStream + rName := acctest.RandString(15) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCloudWatchLogStreamDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudWatchLogStreamConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchLogStreamExists("aws_cloudwatch_log_stream.foobar", &ls), + testAccCheckCloudWatchLogStreamDisappears(&ls, rName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckCloudWatchLogStreamDisappears(ls *cloudwatchlogs.LogStream, lgn string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).cloudwatchlogsconn + opts := &cloudwatchlogs.DeleteLogStreamInput{ + LogGroupName: aws.String(lgn), + LogStreamName: ls.LogStreamName, + } + if _, err := conn.DeleteLogStream(opts); err != nil { + return err + } + return nil + } +} + +func testAccCheckCloudWatchLogStreamExists(n string, ls *cloudwatchlogs.LogStream) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + logGroupName := rs.Primary.Attributes["log_group_name"] + conn := testAccProvider.Meta().(*AWSClient).cloudwatchlogsconn + logGroup, exists, err := lookupCloudWatchLogStream(conn, rs.Primary.ID, logGroupName, nil) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("Bad: LogStream %q does not exist", rs.Primary.ID) + } + + *ls = *logGroup + + return nil + } +} + +func testAccCheckAWSCloudWatchLogStreamDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).cloudwatchlogsconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_cloudwatch_log_stream" { + continue + } + + logGroupName := rs.Primary.Attributes["log_group_name"] + _, exists, err := lookupCloudWatchLogStream(conn, rs.Primary.ID, logGroupName, nil) + if err != nil { + return nil + } + + if exists { + return fmt.Errorf("Bad: LogStream still exists: %q", rs.Primary.ID) + } + + } + + return nil +} + +func TestValidateCloudWatchLogStreamName(t *testing.T) { + validNames := []string{ + "test-log-stream", + "my_sample_log_stream", + "012345678", + "logstream/1234", + } + for _, v := range validNames { + _, errors := validateCloudWatchLogStreamName(v, "name") + if len(errors) != 0 { + t.Fatalf("%q should be a valid CloudWatch LogStream name: %q", v, errors) + } + } + + invalidNames := []string{ + acctest.RandString(513), + "", + "stringwith:colon", + } + for _, v := range invalidNames { + _, errors := validateCloudWatchLogStreamName(v, "name") + if len(errors) == 0 { + t.Fatalf("%q should be an invalid CloudWatch LogStream name", v) + } + } +} + +func testAccAWSCloudWatchLogStreamConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_cloudwatch_log_group" "foobar" { + name = "%s" +} + +resource "aws_cloudwatch_log_stream" "foobar" { + name = "%s" + log_group_name = "${aws_cloudwatch_log_group.foobar.id}" +} +`, rName, rName) +} diff --git a/website/source/docs/providers/aws/r/cloudwatch_log_stream.html.markdown b/website/source/docs/providers/aws/r/cloudwatch_log_stream.html.markdown new file mode 100644 index 000000000..b01d3bb9b --- /dev/null +++ b/website/source/docs/providers/aws/r/cloudwatch_log_stream.html.markdown @@ -0,0 +1,37 @@ +--- +layout: "aws" +page_title: "AWS: aws_cloudwatch_log_stream" +sidebar_current: "docs-aws-resource-cloudwatch-log-stream" +description: |- + Provides a CloudWatch Log Stream resource. +--- + +# aws\_cloudwatch\_log\_stream + +Provides a CloudWatch Log Stream resource. + +## Example Usage + +``` +resource "aws_cloudwatch_log_group" "yada" { + name = "Yada" +} + +resource "aws_cloudwatch_log_stream" "foo" { + name = "SampleLogStream1234" + log_group_name = "${aws_cloudwatch_log_group.yada.name}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the log stream. Must not be longer than 512 characters and must not contain `:` +* `log_group_name` - (Required) The name of the log group under which the log stream is to be created. + +## Attributes Reference + +The following attributes are exported: + +* `arn` - The Amazon Resource Name (ARN) specifying the log stream. \ No newline at end of file diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 4bc264914..7fa4d66dd 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -155,6 +155,10 @@ aws_cloudwatch_log_metric_filter + > + aws_cloudwatch_log_stream + + > aws_cloudwatch_log_subscription_filter