diff --git a/.changelog/17804.txt b/.changelog/17804.txt new file mode 100644 index 000000000000..8efd4f8c8154 --- /dev/null +++ b/.changelog/17804.txt @@ -0,0 +1,3 @@ +```release-note:new-data-source +aws_resourcegroupstaggingapi_resources +``` diff --git a/aws/data_source_aws_resourcegroupstaggingapi_resources.go b/aws/data_source_aws_resourcegroupstaggingapi_resources.go new file mode 100644 index 000000000000..b1c1eebe723c --- /dev/null +++ b/aws/data_source_aws_resourcegroupstaggingapi_resources.go @@ -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} +} diff --git a/aws/data_source_aws_resourcegroupstaggingapi_resources_test.go b/aws/data_source_aws_resourcegroupstaggingapi_resources_test.go new file mode 100644 index 000000000000..8e207676c4c8 --- /dev/null +++ b/aws/data_source_aws_resourcegroupstaggingapi_resources_test.go @@ -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" {} +` + +func testAccDataSourceAwsResourceGroupsTaggingAPIResourcesTagKeyFilterConfig(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" { + 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 +} +` diff --git a/aws/provider.go b/aws/provider.go index 27bf36d6f018..6d128150d3ec 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -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(), diff --git a/infrastructure/repository/labels-service.tf b/infrastructure/repository/labels-service.tf index acd17a275089..2c39a1150afc 100644 --- a/infrastructure/repository/labels-service.tf +++ b/infrastructure/repository/labels-service.tf @@ -161,6 +161,7 @@ variable "service_labels" { "rds", "redshift", "resourcegroups", + "resourcegroupstaggingapi", "robomaker", "route53", "route53domains", diff --git a/website/docs/d/resourcegroupstaggingapi_resources.html.markdown b/website/docs/d/resourcegroupstaggingapi_resources.html.markdown new file mode 100644 index 000000000000..f35daf96fdca --- /dev/null +++ b/website/docs/d/resourcegroupstaggingapi_resources.html.markdown @@ -0,0 +1,71 @@ +--- +subcategory: "Resource Groups Tagging API" +layout: "aws" +page_title: "AWS: aws_resourcegroupstaggingapi_resources" +description: |- + Provides details about resource tagging. +--- + +# Data Source: aws_resourcegroupstaggingapi_resources + +Provides details about resource tagging. + +## Example Usage + +### Get All Resource Tag Mappings + +```terraform +data "aws_resourcegroupstaggingapi_resources" "test" {} +``` + +### Filter By Tag Key and Value + +```terraform +data "aws_resourcegroupstaggingapi_resources" "test" { + tag_filter { + key = "tag-key" + values = ["tag-value-1", "tag-value-2"] + } +} +``` + +### Filter By Resource Type + +```terraform +data "aws_resourcegroupstaggingapi_resources" "test" { + resource_type_filter = ["ec2:instance"] +} +``` + + +## Argument Reference + +The following arguments are supported: + +* `exclude_compliant_resources` - (Optional) Specifies whether to exclude resources that are compliant with the tag policy. You can use this parameter only if the `include_compliance_details` argument is also set to `true`. +* `include_compliance_details` - (Optional) Specifies whether to include details regarding the compliance with the effective tag policy. +* `tag_filter` - (Optional) Specifies a list of Tag Filters (keys and values) to restrict the output to only those resources that have the specified tag and, if included, the specified value. See [Tag Filter](#tag-filter) below. Conflicts with `resource_arn_list`. +* `resource_type_filter` - (Optional) The constraints on the resources that you want returned. The format of each resource type is `service:resourceType`. For example, specifying a resource type of `ec2` returns all Amazon EC2 resources (which includes EC2 instances). Specifying a resource type of `ec2:instance` returns only EC2 instances. +* `resource_arn_list` - (Optional) Specifies a list of ARNs of resources for which you want to retrieve tag data. Conflicts with `filter`. + +### Tag Filter + +A `tag_filter` block supports the following arguments: + +If you do specify `tag_filter`, the response returns only those resources that are currently associated with the specified tag. +If you don't specify a `tag_filter`, the response includes all resources that were ever associated with tags. Resources that currently don't have associated tags are shown with an empty tag set. + +* `key` - (Required) One part of a key-value pair that makes up a tag. +* `values` - (Optional) The optional part of a key-value pair that make up a tag. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `resource_tag_mapping_list` - List of objects matching the search criteria. + * `compliance_details` - List of objects with information that shows whether a resource is compliant with the effective tag policy, including details on any noncompliant tag keys. + * `compliance_status` - Whether the resource is compliant. + * `keys_with_noncompliant_values ` - Set of tag keys with non-compliant tag values. + * `non_compliant_keys ` - Set of non-compliant tag keys. + * `resource_arn` - ARN of the resource. + * `tags` - Map of tags assigned to the resource.