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

resourcegroupstaggingapi_resources - new data source #17804

Merged
merged 17 commits into from
Apr 29, 2021
3 changes: 3 additions & 0 deletions .changelog/17804.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-data-source
aws_resourcegroupstaggingapi_resources
```
193 changes: 193 additions & 0 deletions aws/data_source_aws_resourcegroupstaggingapi_resources.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package aws

import (
"fmt"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
)

func dataSourceAwsResourceGroupsTaggingAPIResources() *schema.Resource {
return &schema.Resource{
Read: dataSourceAwsResourceGroupsTaggingAPIResourcesRead,

Schema: map[string]*schema.Schema{
"exclude_compliant_resources": {
Type: schema.TypeBool,
Optional: true,
},
"include_compliance_details": {
Type: schema.TypeBool,
Optional: true,
},
"resource_arn_list": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
ConflictsWith: []string{"tag_filter"},
},
"resource_type_filters": {
Type: schema.TypeSet,
Optional: true,
MaxItems: 100,
Elem: &schema.Schema{Type: schema.TypeString},
ConflictsWith: []string{"resource_arn_list"},
},
"tag_filter": {
Type: schema.TypeList,
Optional: true,
MaxItems: 50,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"key": {
Type: schema.TypeString,
Required: true,
},
"values": {
Type: schema.TypeSet,
Optional: true,
MaxItems: 20,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
},
},
"resource_tag_mapping_list": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"resource_arn": {
Type: schema.TypeString,
Computed: true,
},
"compliance_details": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"compliance_status": {
Type: schema.TypeBool,
Computed: true,
},
"keys_with_noncompliant_values": {
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"non_compliant_keys": {
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
},
},
"tags": tagsSchemaComputed(),
},
},
},
},
}
}

func dataSourceAwsResourceGroupsTaggingAPIResourcesRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).resourcegroupstaggingapiconn

input := &resourcegroupstaggingapi.GetResourcesInput{}

if v, ok := d.GetOk("include_compliance_details"); ok {
input.IncludeComplianceDetails = aws.Bool(v.(bool))
}

if v, ok := d.GetOk("exclude_compliant_resources"); ok {
input.ExcludeCompliantResources = aws.Bool(v.(bool))
}

if v, ok := d.GetOk("resource_arn_list"); ok && v.(*schema.Set).Len() > 0 {
input.ResourceARNList = expandStringSet(v.(*schema.Set))
}

if v, ok := d.GetOk("tag_filter"); ok {
input.TagFilters = expandAwsResourceGroupsTaggingAPITagFilters(v.([]interface{}))
}

if v, ok := d.GetOk("resource_type_filters"); ok && v.(*schema.Set).Len() > 0 {
input.ResourceTypeFilters = expandStringSet(v.(*schema.Set))
}

var taggings []*resourcegroupstaggingapi.ResourceTagMapping

err := conn.GetResourcesPages(input, func(page *resourcegroupstaggingapi.GetResourcesOutput, lastPage bool) bool {
if page == nil {
return !lastPage
}

taggings = append(taggings, page.ResourceTagMappingList...)
return !lastPage
})
if err != nil {
return fmt.Errorf("error getting Resource Groups Tags API Resources: %w", err)
}

d.SetId(meta.(*AWSClient).partition)

if err := d.Set("resource_tag_mapping_list", flattenAwsResourceGroupsTaggingAPIResourcesTagMappingList(taggings)); err != nil {
return fmt.Errorf("error setting resource tag mapping list: %w", err)
}

return nil
}

func expandAwsResourceGroupsTaggingAPITagFilters(filters []interface{}) []*resourcegroupstaggingapi.TagFilter {
result := make([]*resourcegroupstaggingapi.TagFilter, len(filters))

for i, filter := range filters {
m := filter.(map[string]interface{})

result[i] = &resourcegroupstaggingapi.TagFilter{
Key: aws.String(m["key"].(string)),
}

if v, ok := m["values"]; ok && v.(*schema.Set).Len() > 0 {
result[i].Values = expandStringSet(v.(*schema.Set))
}
}

return result
}

func flattenAwsResourceGroupsTaggingAPIResourcesTagMappingList(list []*resourcegroupstaggingapi.ResourceTagMapping) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(list))

for _, i := range list {
l := map[string]interface{}{
"resource_arn": aws.StringValue(i.ResourceARN),
"tags": keyvaluetags.ResourcegroupstaggingapiKeyValueTags(i.Tags).Map(),
}

if i.ComplianceDetails != nil {
l["compliance_details"] = flattenAwsResourceGroupsTaggingAPIComplianceDetails(i.ComplianceDetails)
}

result = append(result, l)
}

return result
}

func flattenAwsResourceGroupsTaggingAPIComplianceDetails(details *resourcegroupstaggingapi.ComplianceDetails) []map[string]interface{} {
if details == nil {
return []map[string]interface{}{}
}

m := map[string]interface{}{
"compliance_status": aws.BoolValue(details.ComplianceStatus),
"keys_with_noncompliant_values": flattenStringSet(details.KeysWithNoncompliantValues),
"non_compliant_keys": flattenStringSet(details.NoncompliantKeys),
}

return []map[string]interface{}{m}
}
183 changes: 183 additions & 0 deletions aws/data_source_aws_resourcegroupstaggingapi_resources_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package aws

import (
"fmt"
"testing"

"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAccDataSourceAwsResourceGroupsTaggingAPIResources_basic(t *testing.T) {
dataSourceName := "data.aws_resourcegroupstaggingapi_resources.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ErrorCheck: testAccErrorCheck(t, resourcegroupstaggingapi.EndpointsID),
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAwsResourceGroupsTaggingAPIResourcesBasicConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet(dataSourceName, "resource_tag_mapping_list.#"),
resource.TestCheckResourceAttrSet(dataSourceName, "resource_tag_mapping_list.0.resource_arn"),
),
},
},
})
}

func TestAccDataSourceAwsResourceGroupsTaggingAPIResources_tag_key_filter(t *testing.T) {
dataSourceName := "data.aws_resourcegroupstaggingapi_resources.test"
resourceName := "aws_api_gateway_rest_api.test"
rName := acctest.RandomWithPrefix("tf-acc-test")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ErrorCheck: testAccErrorCheck(t, resourcegroupstaggingapi.EndpointsID),
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAwsResourceGroupsTaggingAPIResourcesTagKeyFilterConfig(rName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckTypeSetElemNestedAttrs(dataSourceName, "resource_tag_mapping_list.*", map[string]string{
"tags.Key": rName,
}),
resource.TestCheckTypeSetElemAttrPair(dataSourceName, "resource_tag_mapping_list.*.resource_arn", resourceName, "arn"),
),
},
},
})
}

func TestAccDataSourceAwsResourceGroupsTaggingAPIResources_compliance(t *testing.T) {
dataSourceName := "data.aws_resourcegroupstaggingapi_resources.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ErrorCheck: testAccErrorCheck(t, resourcegroupstaggingapi.EndpointsID),
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAwsResourceGroupsTaggingAPIResourcesComplianceConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(dataSourceName, "resource_tag_mapping_list.0.compliance_details.#", "1"),
resource.TestCheckResourceAttr(dataSourceName, "resource_tag_mapping_list.0.compliance_details.0.compliance_status", "true"),
resource.TestCheckResourceAttrSet(dataSourceName, "resource_tag_mapping_list.0.resource_arn"),
),
},
},
})
}

func TestAccDataSourceAwsResourceGroupsTaggingAPIResources_resource_type_filters(t *testing.T) {
dataSourceName := "data.aws_resourcegroupstaggingapi_resources.test"
resourceName := "aws_api_gateway_rest_api.test"
rName := acctest.RandomWithPrefix("tf-acc-test")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ErrorCheck: testAccErrorCheck(t, resourcegroupstaggingapi.EndpointsID),
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAwsResourceGroupsTaggingAPIResourcesResourceTypeFiltersConfig(rName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckTypeSetElemNestedAttrs(dataSourceName, "resource_tag_mapping_list.*", map[string]string{
"tags.Key": rName,
}),
resource.TestCheckTypeSetElemAttrPair(dataSourceName, "resource_tag_mapping_list.*.resource_arn", resourceName, "arn"),
),
},
},
})
}

func TestAccDataSourceAwsResourceGroupsTaggingAPIResources_resource_arn_list(t *testing.T) {
dataSourceName := "data.aws_resourcegroupstaggingapi_resources.test"
resourceName := "aws_api_gateway_rest_api.test"
rName := acctest.RandomWithPrefix("tf-acc-test")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ErrorCheck: testAccErrorCheck(t, resourcegroupstaggingapi.EndpointsID),
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAwsResourceGroupsTaggingAPIResourcesResourceARNListConfig(rName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckTypeSetElemNestedAttrs(dataSourceName, "resource_tag_mapping_list.*", map[string]string{
"tags.Key": rName,
}),
resource.TestCheckTypeSetElemAttrPair(dataSourceName, "resource_tag_mapping_list.*.resource_arn", resourceName, "arn"),
),
},
},
})
}

const testAccDataSourceAwsResourceGroupsTaggingAPIResourcesBasicConfig = `
data "aws_resourcegroupstaggingapi_resources" "test" {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty configuration is taking 30+ minutes to paginate in our testing accounts 😢 In this case, I think it is probably okay to omit the testing since the API doesn't provide any sort of "maximum results" parameter.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i was afraid of this. i'm ok with omitting it. maybe hide it behind a flag?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a similar issue with aws_ami where unfiltered results would result in a huge result set as well. We skip including that testing with the hope that the unfiltered API operation would operate the same as a filtered one. Not aware of any particular issues with that in the last 3 years. We could put this behind an environment variable, but it is probably okay without it.

`

func testAccDataSourceAwsResourceGroupsTaggingAPIResourcesTagKeyFilterConfig(rName string) string {
return fmt.Sprintf(`
resource "aws_api_gateway_rest_api" "test" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API Gateway REST APIs can be slightly problematic:

  • Throttled to 2 deletions/minute
  • EDGE endpoints (default) are unavailable in GovCloud

Going to switch this to an easier resource such as a VPC to see if that cooperates a little better. 👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i tinkered a lot with this. was the simplest one i could think of at the time. i remember trying vpc but dont remember why i dropped it. IIRC it didn't propagate and results were empty.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be having really good luck today as it seems to be passing with aws_vpc resources and random tags. 😄

name = %[1]q

tags = {
Key = %[1]q
}
}

data "aws_resourcegroupstaggingapi_resources" "test" {
tag_filter {
key = "Key"
}

depends_on = [aws_api_gateway_rest_api.test]
}
`, rName)
}

func testAccDataSourceAwsResourceGroupsTaggingAPIResourcesResourceTypeFiltersConfig(rName string) string {
return fmt.Sprintf(`
resource "aws_api_gateway_rest_api" "test" {
name = %[1]q

tags = {
Key = %[1]q
}
}

data "aws_resourcegroupstaggingapi_resources" "test" {
resource_type_filters = ["apigateway"]

depends_on = [aws_api_gateway_rest_api.test]
}
`, rName)
}

func testAccDataSourceAwsResourceGroupsTaggingAPIResourcesResourceARNListConfig(rName string) string {
return fmt.Sprintf(`
resource "aws_api_gateway_rest_api" "test" {
name = %[1]q

tags = {
Key = %[1]q
}
}

data "aws_resourcegroupstaggingapi_resources" "test" {
resource_arn_list = [aws_api_gateway_rest_api.test.arn]
}
`, rName)
}

const testAccDataSourceAwsResourceGroupsTaggingAPIResourcesComplianceConfig = `
data "aws_resourcegroupstaggingapi_resources" "test" {
include_compliance_details = true
exclude_compliant_resources = false
}
`
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ func Provider() *schema.Provider {
"aws_redshift_service_account": dataSourceAwsRedshiftServiceAccount(),
"aws_region": dataSourceAwsRegion(),
"aws_regions": dataSourceAwsRegions(),
"aws_resourcegroupstaggingapi_resources": dataSourceAwsResourceGroupsTaggingAPIResources(),
"aws_route": dataSourceAwsRoute(),
"aws_route_table": dataSourceAwsRouteTable(),
"aws_route_tables": dataSourceAwsRouteTables(),
Expand Down
1 change: 1 addition & 0 deletions infrastructure/repository/labels-service.tf
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ variable "service_labels" {
"rds",
"redshift",
"resourcegroups",
"resourcegroupstaggingapi",
"robomaker",
"route53",
"route53domains",
Expand Down
Loading