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

r/aws_cloudwatch_metric_stream support statistics_configurations resource #24882

Merged
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
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 support for statistics_configurations field
```
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_configurations": {
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_metrics": {
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_configurations"); 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_configurations", flattenMetricStreamStatisticsConfigurations(output.StatisticsConfigurations)); err != nil {
return diag.FromErr(fmt.Errorf("error setting statistics_configurations 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_metrics"].(*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_metrics": 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_configurations.#", "2"),
),
},
{
Config: testAccMetricStreamAdditionalStatisticsConfig(rName, "PR(:50)"),
Check: resource.ComposeTestCheckFunc(
testAccCheckMetricStreamExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "statistics_configurations.#", "2"),
),
},
{
Config: testAccMetricStreamAdditionalStatisticsConfig(rName, "TS(50.5:)"),
Check: resource.ComposeTestCheckFunc(
testAccCheckMetricStreamExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "statistics_configurations.#", "2"),
),
},
{
Config: testAccMetricStreamAdditionalStatisticsConfig(rName, "TC(1:100)"),
Check: resource.ComposeTestCheckFunc(
testAccCheckMetricStreamExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "statistics_configurations.#", "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_configurations {
additional_statistics = [
"p1", "tm99"
]

include_metrics {
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
}
}

statistics_configurations {
additional_statistics = [
%[2]q
]

include_metrics {
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
}
}
}
`, rName, stat)
}
19 changes: 17 additions & 2 deletions website/docs/r/cloudwatch_metric_stream.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,30 @@ 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_configurations` - (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.

### `exclude_filter`
### Nested Fields

#### `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_metrics` - (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