Skip to content

Commit

Permalink
Merge pull request #24882 from mcgealy/f-metric_stream_additional_sta…
Browse files Browse the repository at this point in the history
…tistics

r/aws_cloudwatch_metric_stream support statistics_configurations resource
  • Loading branch information
ewbankkit authored May 24, 2022
2 parents 3c91ed0 + c258c69 commit 8a01f81
Show file tree
Hide file tree
Showing 4 changed files with 307 additions and 2 deletions.
3 changes: 3 additions & 0 deletions 24882.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
r/aws_cloudwatch_metric_stream: Add `statistics_configuration` argument
```
145 changes: 145 additions & 0 deletions internal/service/cloudwatch/metric_stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
"github.com/hashicorp/terraform-provider-aws/internal/create"
"github.com/hashicorp/terraform-provider-aws/internal/flex"
tftags "github.com/hashicorp/terraform-provider-aws/internal/tags"
"github.com/hashicorp/terraform-provider-aws/internal/verify"
)
Expand Down Expand Up @@ -113,6 +114,55 @@ func ResourceMetricStream() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"statistics_configuration": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"additional_statistics": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.All(
validation.Any(
validation.StringMatch(
regexp.MustCompile(`(^IQM$)|(^(p|tc|tm|ts|wm)(100|\d{1,2})(\.\d{0,10})?$)|(^[ou]\d+(\.\d*)?$)`),
"invalid statistic, see: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Statistics-definitions.html",
),
validation.StringMatch(
regexp.MustCompile(`^(TM|TC|TS|WM)\(((((\d{1,2})(\.\d{0,10})?|100(\.0{0,10})?)%)?:((\d{1,2})(\.\d{0,10})?|100(\.0{0,10})?)%|((\d{1,2})(\.\d{0,10})?|100(\.0{0,10})?)%:(((\d{1,2})(\.\d{0,10})?|100(\.0{0,10})?)%)?)\)|(TM|TC|TS|WM|PR)\(((\d+(\.\d{0,10})?|(\d+(\.\d{0,10})?[Ee][+-]?\d+)):((\d+(\.\d{0,10})?|(\d+(\.\d{0,10})?[Ee][+-]?\d+)))?|((\d+(\.\d{0,10})?|(\d+(\.\d{0,10})?[Ee][+-]?\d+)))?:(\d+(\.\d{0,10})?|(\d+(\.\d{0,10})?[Ee][+-]?\d+)))\)$`),
"invalid statistic, see: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Statistics-definitions.html",
),
),
validation.StringDoesNotMatch(
regexp.MustCompile(`^p0(\.0{0,10})?|p100(\.\d{0,10})?$`),
"invalid statistic, see: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Statistics-definitions.html",
),
),
},
},
"include_metric": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"metric_name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(1, 255),
},
"namespace": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(1, 255),
},
},
},
},
},
},
},
"tags": tftags.TagsSchema(),
"tags_all": tftags.TagsSchemaComputed(),
},
Expand Down Expand Up @@ -145,6 +195,10 @@ func resourceMetricStreamCreate(ctx context.Context, d *schema.ResourceData, met
params.ExcludeFilters = expandMetricStreamFilters(v.(*schema.Set))
}

if v, ok := d.GetOk("statistics_configuration"); ok && v.(*schema.Set).Len() > 0 {
params.StatisticsConfigurations = expandMetricStreamStatisticsConfigurations(v.(*schema.Set))
}

log.Printf("[DEBUG] Putting CloudWatch Metric Stream: %#v", params)
output, err := conn.PutMetricStreamWithContext(ctx, &params)

Expand Down Expand Up @@ -224,6 +278,12 @@ func resourceMetricStreamRead(ctx context.Context, d *schema.ResourceData, meta
}
}

if output.StatisticsConfigurations != nil {
if err := d.Set("statistics_configuration", flattenMetricStreamStatisticsConfigurations(output.StatisticsConfigurations)); err != nil {
return diag.FromErr(fmt.Errorf("error setting statistics_configuration error: %w", err))
}
}

tags, err := ListTags(conn, aws.StringValue(output.Arn))

// Some partitions (i.e., ISO) may not support tagging, giving error
Expand Down Expand Up @@ -312,3 +372,88 @@ func flattenMetricStreamFilters(s []*cloudwatch.MetricStreamFilter) []map[string

return nil
}

func expandMetricStreamStatisticsConfigurations(s *schema.Set) []*cloudwatch.MetricStreamStatisticsConfiguration {
var configurations []*cloudwatch.MetricStreamStatisticsConfiguration

for _, configurationRaw := range s.List() {
configuration := &cloudwatch.MetricStreamStatisticsConfiguration{}
mConfiguration := configurationRaw.(map[string]interface{})

if v, ok := mConfiguration["additional_statistics"].(*schema.Set); ok && v.Len() > 0 {
log.Printf("[DEBUG] CloudWatch Metric Stream StatisticsConfigurations additional_statistics: %#v", v)
configuration.AdditionalStatistics = flex.ExpandStringSet(v)
}

if v, ok := mConfiguration["include_metric"].(*schema.Set); ok && v.Len() > 0 {
log.Printf("[DEBUG] CloudWatch Metric Stream StatisticsConfigurations include_metrics: %#v", v)
configuration.IncludeMetrics = expandMetricStreamStatisticsConfigurationsIncludeMetrics(v)
}

configurations = append(configurations, configuration)

}

log.Printf("[DEBUG] statistics_configurations: %#v", configurations)

if len(configurations) > 0 {
return configurations
}

return nil
}

func expandMetricStreamStatisticsConfigurationsIncludeMetrics(metrics *schema.Set) []*cloudwatch.MetricStreamStatisticsMetric {
var includeMetrics []*cloudwatch.MetricStreamStatisticsMetric

for _, metricRaw := range metrics.List() {
metric := &cloudwatch.MetricStreamStatisticsMetric{}
mMetric := metricRaw.(map[string]interface{})

if v, ok := mMetric["metric_name"].(string); ok && v != "" {
metric.MetricName = aws.String(v)
}

if v, ok := mMetric["namespace"].(string); ok && v != "" {
metric.Namespace = aws.String(v)
}

includeMetrics = append(includeMetrics, metric)
}

if len(includeMetrics) > 0 {
return includeMetrics
}

return nil
}

func flattenMetricStreamStatisticsConfigurations(configurations []*cloudwatch.MetricStreamStatisticsConfiguration) []map[string]interface{} {
flatConfigurations := make([]map[string]interface{}, len(configurations))

for i, configuration := range configurations {
flatConfiguration := map[string]interface{}{
"additional_statistics": flex.FlattenStringSet(configuration.AdditionalStatistics),
"include_metric": flattenMetricStreamStatisticsConfigurationsIncludeMetrics(configuration.IncludeMetrics),
}

flatConfigurations[i] = flatConfiguration
}

return flatConfigurations
}

func flattenMetricStreamStatisticsConfigurationsIncludeMetrics(metrics []*cloudwatch.MetricStreamStatisticsMetric) []map[string]interface{} {
flatMetrics := make([]map[string]interface{}, len(metrics))

for i, metric := range metrics {
flatMetric := map[string]interface{}{
"metric_name": aws.StringValue(metric.MetricName),
"namespace": aws.StringValue(metric.Namespace),
}

flatMetrics[i] = flatMetric
}

return flatMetrics
}
109 changes: 109 additions & 0 deletions internal/service/cloudwatch/metric_stream_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cloudwatch_test

import (
"fmt"
"regexp"
"strings"
"testing"

Expand Down Expand Up @@ -255,6 +256,77 @@ func TestAccCloudWatchMetricStream_tags(t *testing.T) {
})
}

func TestAccCloudWatchMetricStream_additional_statistics(t *testing.T) {
resourceName := "aws_cloudwatch_metric_stream.test"
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ErrorCheck: acctest.ErrorCheck(t, cloudwatch.EndpointsID),
ProviderFactories: acctest.ProviderFactories,
CheckDestroy: testAccCheckMetricStreamDestroy,
Steps: []resource.TestStep{
{
Config: testAccMetricStreamAdditionalStatisticsConfig(rName, "p0"),
ExpectError: regexp.MustCompile(`invalid statistic, see: https:\/\/docs\.aws\.amazon\.com\/.*`),
},
{
Config: testAccMetricStreamAdditionalStatisticsConfig(rName, "p100"),
ExpectError: regexp.MustCompile(`invalid statistic, see: https:\/\/docs\.aws\.amazon\.com\/.*`),
},
{
Config: testAccMetricStreamAdditionalStatisticsConfig(rName, "p"),
ExpectError: regexp.MustCompile(`invalid statistic, see: https:\/\/docs\.aws\.amazon\.com\/.*`),
},
{
Config: testAccMetricStreamAdditionalStatisticsConfig(rName, "tm"),
ExpectError: regexp.MustCompile(`invalid statistic, see: https:\/\/docs\.aws\.amazon\.com\/.*`),
},
{
Config: testAccMetricStreamAdditionalStatisticsConfig(rName, "tc()"),
ExpectError: regexp.MustCompile(`invalid statistic, see: https:\/\/docs\.aws\.amazon\.com\/.*`),
},
{
Config: testAccMetricStreamAdditionalStatisticsConfig(rName, "p99.12345678901"),
ExpectError: regexp.MustCompile(`invalid statistic, see: https:\/\/docs\.aws\.amazon\.com\/.*`),
},
{
Config: testAccMetricStreamAdditionalStatisticsConfig(rName, "IQM"),
Check: resource.ComposeTestCheckFunc(
testAccCheckMetricStreamExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "statistics_configuration.#", "2"),
),
},
{
Config: testAccMetricStreamAdditionalStatisticsConfig(rName, "PR(:50)"),
Check: resource.ComposeTestCheckFunc(
testAccCheckMetricStreamExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "statistics_configuration.#", "2"),
),
},
{
Config: testAccMetricStreamAdditionalStatisticsConfig(rName, "TS(50.5:)"),
Check: resource.ComposeTestCheckFunc(
testAccCheckMetricStreamExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "statistics_configuration.#", "2"),
),
},
{
Config: testAccMetricStreamAdditionalStatisticsConfig(rName, "TC(1:100)"),
Check: resource.ComposeTestCheckFunc(
testAccCheckMetricStreamExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "statistics_configuration.#", "2"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccCheckMetricStreamGeneratedNamePrefix(resource, prefix string) resource.TestCheckFunc {
return func(s *terraform.State) error {
r, ok := s.RootModule().Resources[resource]
Expand Down Expand Up @@ -568,3 +640,40 @@ resource "aws_cloudwatch_metric_stream" "test" {
}
`, rName)
}

func testAccMetricStreamAdditionalStatisticsConfig(rName string, stat string) string {
return fmt.Sprintf(`
data "aws_partition" "current" {}
data "aws_region" "current" {}
data "aws_caller_identity" "current" {}
resource "aws_cloudwatch_metric_stream" "test" {
name = %[1]q
role_arn = "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:role/MyRole"
firehose_arn = "arn:${data.aws_partition.current.partition}:firehose:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:deliverystream/MyFirehose"
output_format = "json"
statistics_configuration {
additional_statistics = [
"p1", "tm99"
]
include_metric {
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
}
}
statistics_configuration {
additional_statistics = [
%[2]q
]
include_metric {
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
}
}
}
`, rName, stat)
}
52 changes: 50 additions & 2 deletions website/docs/r/cloudwatch_metric_stream.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Provides a CloudWatch Metric Stream resource.

## Example Usage

### Filters

```terraform
resource "aws_cloudwatch_metric_stream" "main" {
name = "my-metric-stream"
Expand Down Expand Up @@ -137,6 +139,39 @@ resource "aws_kinesis_firehose_delivery_stream" "s3_stream" {
}
```

### Additional Statistics

```terraform
resource "aws_cloudwatch_metric_stream" "main" {
name = "my-metric-stream"
role_arn = aws_iam_role.metric_stream_to_firehose.arn
firehose_arn = aws_kinesis_firehose_delivery_stream.s3_stream.arn
output_format = "json"
statistics_configuration {
additional_statistics = [
"p1", "tm99"
]
include_metric {
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
}
}
statistics_configuration {
additional_statistics = [
"TS(50.5:)"
]
include_metric {
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
}
}
}
```

## Argument Reference

The following arguments are required:
Expand All @@ -152,15 +187,28 @@ The following arguments are optional:
* `name` - (Optional, Forces new resource) Friendly name of the metric stream. If omitted, Terraform will assign a random, unique name. Conflicts with `name_prefix`.
* `name_prefix` - (Optional, Forces new resource) Creates a unique friendly name beginning with the specified prefix. Conflicts with `name`.
* `tags` - (Optional) Map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level.
* `statistics_configuration` - (Optional) For each entry in this array, you specify one or more metrics and the list of additional statistics to stream for those metrics. The additional statistics that you can stream depend on the stream's `output_format`. If the OutputFormat is `json`, you can stream any additional statistic that is supported by CloudWatch, listed in [CloudWatch statistics definitions](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Statistics-definitions.html.html). If the OutputFormat is `opentelemetry0.7`, you can stream percentile statistics (p99 etc.). See details below.

### Nested Fields

### `exclude_filter`
#### `exclude_filter`

* `namespace` - (Required) Name of the metric namespace in the filter.

### `include_filter`
#### `include_filter`

* `namespace` - (Required) Name of the metric namespace in the filter.

#### `statistics_configurations`

* `additional_statistics` - (Required) The additional statistics to stream for the metrics listed in `include_metrics`.
* `include_metric` - (Required) An array that defines the metrics that are to have additional statistics streamed. See details below.

#### `include_metrics`

* `metric_name` - (Required) The name of the metric.
* `namespace` - (Required) The namespace of the metric.

## Attributes Reference

In addition to all arguments above, the following attributes are exported:
Expand Down

0 comments on commit 8a01f81

Please sign in to comment.