From e0f175728c94f3523719b9cc7fcd159aac715c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Lamand=C3=A9?= Date: Tue, 6 Apr 2021 10:04:39 +0200 Subject: [PATCH 1/2] Introduce data source aws_iam_roles --- aws/data_source_aws_iam_roles.go | 118 ++++++++++++++ aws/data_source_aws_iam_roles_test.go | 205 +++++++++++++++++++++++++ aws/provider.go | 1 + website/docs/d/iam_roles.html.markdown | 82 ++++++++++ 4 files changed, 406 insertions(+) create mode 100644 aws/data_source_aws_iam_roles.go create mode 100644 aws/data_source_aws_iam_roles_test.go create mode 100644 website/docs/d/iam_roles.html.markdown diff --git a/aws/data_source_aws_iam_roles.go b/aws/data_source_aws_iam_roles.go new file mode 100644 index 00000000000..92e49265973 --- /dev/null +++ b/aws/data_source_aws_iam_roles.go @@ -0,0 +1,118 @@ +package aws + +import ( // nosemgrep: aws-sdk-go-multiple-service-imports + "fmt" + "log" + "path/filepath" // filepath for glob matching + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/iam" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceAwsIAMRoles() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsIAMRolesRead, + + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + "path_prefix": { + Type: schema.TypeString, + Optional: true, + }, + "arns": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + "names": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + }, + } +} + +func dataSourceAwsIAMRolesRead(d *schema.ResourceData, meta interface{}) error { + iamconn := meta.(*AWSClient).iamconn + req := &iam.ListRolesInput{} + + if pathPrefix, hasPathPrefix := d.GetOk("path_prefix"); hasPathPrefix { + req.PathPrefix = aws.String(pathPrefix.(string)) + } + + filters, hasFilters := d.GetOk("filter") + filtersSet := []*ec2.Filter{} + + if hasFilters { + filtersSet = buildAwsDataSourceFilters(filters.(*schema.Set)) + log.Printf("[DEBUG] Has filters : %s", filtersSet) + // Only filters using the name "role-name" are currently supported + for _, f := range filtersSet { + if "role-name" != aws.StringValue(f.Name) { + return fmt.Errorf("Provided filters does not match supported names. See the documentation of this data source for supported filters.") + } + } + } else { + log.Printf("[DEBUG] No filter") + } + + roles := []*iam.Role{} + + err := iamconn.ListRolesPages( + req, + func(page *iam.ListRolesOutput, lastPage bool) bool { + for _, role := range page.Roles { + if hasFilters { + log.Printf("[DEBUG] Found Role '%s' to be checked against filters", *role.RoleName) + matchAllFilters := true + for _, f := range filtersSet { + for _, filterValue := range f.Values { + // must match all values + if matched, _ := filepath.Match(*filterValue, *role.RoleName); !matched { + log.Printf("[DEBUG] RoleName '%s' does not match filter '%s'", *role.RoleName, *filterValue) + matchAllFilters = false + } + } + } + if matchAllFilters { + roles = append(roles, role) + } + } else { + log.Printf("[DEBUG] Found Role '%s'", *role.RoleName) + roles = append(roles, role) + } + } + return !lastPage + }, + ) + if err != nil { + return fmt.Errorf("error reading IAM roles : %w", err) + } + + if len(roles) == 0 { + log.Printf("[WARN] couldn't find any IAM role matching the provided parameters") + } + + arns := []string{} + names := []string{} + for _, v := range roles { + arns = append(arns, aws.StringValue(v.Arn)) + names = append(names, aws.StringValue(v.RoleName)) + } + + if err := d.Set("arns", arns); err != nil { + return fmt.Errorf("error setting arns: %w", err) + } + if err := d.Set("names", names); err != nil { + return fmt.Errorf("error setting names: %w", err) + } + + d.SetId(meta.(*AWSClient).region) + + return nil +} diff --git a/aws/data_source_aws_iam_roles_test.go b/aws/data_source_aws_iam_roles_test.go new file mode 100644 index 00000000000..af7ca40fa07 --- /dev/null +++ b/aws/data_source_aws_iam_roles_test.go @@ -0,0 +1,205 @@ +package aws + +import ( + "fmt" + "regexp" + "strconv" + "testing" + + "github.com/aws/aws-sdk-go/service/iam" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccAWSDataSourceIAMRoles_basic(t *testing.T) { + dataSourceName := "data.aws_iam_roles.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, iam.EndpointsID), + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSourceIAMRolesConfig_basic(), + Check: resource.ComposeTestCheckFunc( + // testCheckResourceAttrIsNot(dataSourceName, "names.#", "0"), + resource.TestMatchResourceAttr(dataSourceName, "names.#", regexp.MustCompile("[^0].*$")), + ), + }, + }, + }) +} + +func TestAccAWSDataSourceIAMRoles_filterRoleName(t *testing.T) { + rCount := strconv.Itoa(acctest.RandIntRange(1, 4)) + rName := acctest.RandomWithPrefix("tf-acc-test") + dataSourceName := "data.aws_iam_roles.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, iam.EndpointsID), + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSourceIAMRolesConfig_filterByName(rCount, rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "names.#", rCount), + resource.TestCheckResourceAttr(dataSourceName, "arns.#", rCount), + ), + }, + }, + }) +} + +func TestAccAWSDataSourceIAMRoles_pathPrefix(t *testing.T) { + rCount := strconv.Itoa(acctest.RandIntRange(1, 4)) + rName := acctest.RandomWithPrefix("tf-acc-test") + rPathPrefix := acctest.RandomWithPrefix("tf-acc-path") + dataSourceName := "data.aws_iam_roles.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, iam.EndpointsID), + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSourceIAMRolesConfig_pathPrefix(rCount, rName, rPathPrefix), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "names.#", rCount), + resource.TestCheckResourceAttr(dataSourceName, "arns.#", rCount), + ), + }, + }, + }) +} + +func TestAccAWSDataSourceIAMRoles_pathPrefixAndFilterRoleName(t *testing.T) { + rCount := strconv.Itoa(acctest.RandIntRange(1, 4)) + rName := acctest.RandomWithPrefix("tf-acc-test") + rPathPrefix := acctest.RandomWithPrefix("tf-acc-path") + dataSourceName := "data.aws_iam_roles.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, iam.EndpointsID), + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAWSDataSourceIAMRolesConfig_pathPrefixAndFilterRoleName(rCount, rName, rPathPrefix), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "names.#", rCount), + resource.TestCheckResourceAttr(dataSourceName, "arns.#", rCount), + ), + }, + }, + }) +} + +func testAccAWSDataSourceIAMRolesConfig_basic() string { + return ` +data "aws_iam_roles" "test" {} +` +} + +func testAccAWSDataSourceIAMRolesConfig_filterByName(rCount string, rName string) string { + return fmt.Sprintf(` + +resource "aws_iam_role" "test" { + count = %[1]s + name = "%[2]s-${count.index}-role" + + assume_role_policy = < Date: Wed, 18 Aug 2021 00:09:56 -0400 Subject: [PATCH 2/2] CR updates --- .changelog/18585.txt | 3 + aws/data_source_aws_iam_roles.go | 110 ++++++++++--------------- aws/data_source_aws_iam_roles_test.go | 81 +++++++++++------- website/docs/d/iam_roles.html.markdown | 59 +++++++------ 4 files changed, 131 insertions(+), 122 deletions(-) create mode 100644 .changelog/18585.txt diff --git a/.changelog/18585.txt b/.changelog/18585.txt new file mode 100644 index 00000000000..65437a12584 --- /dev/null +++ b/.changelog/18585.txt @@ -0,0 +1,3 @@ +```release-note:new-data-source +aws_iam_roles +``` diff --git a/aws/data_source_aws_iam_roles.go b/aws/data_source_aws_iam_roles.go index 92e49265973..67a9d536e63 100644 --- a/aws/data_source_aws_iam_roles.go +++ b/aws/data_source_aws_iam_roles.go @@ -1,14 +1,13 @@ package aws -import ( // nosemgrep: aws-sdk-go-multiple-service-imports +import ( "fmt" - "log" - "path/filepath" // filepath for glob matching + "regexp" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/iam" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) func dataSourceAwsIAMRoles() *schema.Resource { @@ -16,103 +15,80 @@ func dataSourceAwsIAMRoles() *schema.Resource { Read: dataSourceAwsIAMRolesRead, Schema: map[string]*schema.Schema{ - "filter": dataSourceFiltersSchema(), - "path_prefix": { - Type: schema.TypeString, - Optional: true, - }, "arns": { Type: schema.TypeSet, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, + }, + "name_regex": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsValidRegExp, }, "names": { Type: schema.TypeSet, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, + }, + "path_prefix": { + Type: schema.TypeString, + Optional: true, }, }, } } func dataSourceAwsIAMRolesRead(d *schema.ResourceData, meta interface{}) error { - iamconn := meta.(*AWSClient).iamconn - req := &iam.ListRolesInput{} + conn := meta.(*AWSClient).iamconn - if pathPrefix, hasPathPrefix := d.GetOk("path_prefix"); hasPathPrefix { - req.PathPrefix = aws.String(pathPrefix.(string)) + input := &iam.ListRolesInput{} + + if v, ok := d.GetOk("path_prefix"); ok { + input.PathPrefix = aws.String(v.(string)) } - filters, hasFilters := d.GetOk("filter") - filtersSet := []*ec2.Filter{} + var results []*iam.Role - if hasFilters { - filtersSet = buildAwsDataSourceFilters(filters.(*schema.Set)) - log.Printf("[DEBUG] Has filters : %s", filtersSet) - // Only filters using the name "role-name" are currently supported - for _, f := range filtersSet { - if "role-name" != aws.StringValue(f.Name) { - return fmt.Errorf("Provided filters does not match supported names. See the documentation of this data source for supported filters.") - } + err := conn.ListRolesPages(input, func(page *iam.ListRolesOutput, lastPage bool) bool { + if page == nil { + return !lastPage } - } else { - log.Printf("[DEBUG] No filter") - } - roles := []*iam.Role{} - - err := iamconn.ListRolesPages( - req, - func(page *iam.ListRolesOutput, lastPage bool) bool { - for _, role := range page.Roles { - if hasFilters { - log.Printf("[DEBUG] Found Role '%s' to be checked against filters", *role.RoleName) - matchAllFilters := true - for _, f := range filtersSet { - for _, filterValue := range f.Values { - // must match all values - if matched, _ := filepath.Match(*filterValue, *role.RoleName); !matched { - log.Printf("[DEBUG] RoleName '%s' does not match filter '%s'", *role.RoleName, *filterValue) - matchAllFilters = false - } - } - } - if matchAllFilters { - roles = append(roles, role) - } - } else { - log.Printf("[DEBUG] Found Role '%s'", *role.RoleName) - roles = append(roles, role) - } + for _, role := range page.Roles { + if role == nil { + continue } - return !lastPage - }, - ) + + if v, ok := d.GetOk("name_regex"); ok && !regexp.MustCompile(v.(string)).MatchString(aws.StringValue(role.RoleName)) { + continue + } + + results = append(results, role) + } + + return !lastPage + }) + if err != nil { - return fmt.Errorf("error reading IAM roles : %w", err) + return fmt.Errorf("error reading IAM roles: %w", err) } - if len(roles) == 0 { - log.Printf("[WARN] couldn't find any IAM role matching the provided parameters") - } + d.SetId(meta.(*AWSClient).region) + + var arns, names []string - arns := []string{} - names := []string{} - for _, v := range roles { - arns = append(arns, aws.StringValue(v.Arn)) - names = append(names, aws.StringValue(v.RoleName)) + for _, r := range results { + arns = append(arns, aws.StringValue(r.Arn)) + names = append(names, aws.StringValue(r.RoleName)) } if err := d.Set("arns", arns); err != nil { return fmt.Errorf("error setting arns: %w", err) } + if err := d.Set("names", names); err != nil { return fmt.Errorf("error setting names: %w", err) } - d.SetId(meta.(*AWSClient).region) - return nil } diff --git a/aws/data_source_aws_iam_roles_test.go b/aws/data_source_aws_iam_roles_test.go index af7ca40fa07..84d0da97d45 100644 --- a/aws/data_source_aws_iam_roles_test.go +++ b/aws/data_source_aws_iam_roles_test.go @@ -11,17 +11,17 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) -func TestAccAWSDataSourceIAMRoles_basic(t *testing.T) { +func TestAccAWSIAMRolesDataSource_basic(t *testing.T) { dataSourceName := "data.aws_iam_roles.test" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ErrorCheck: testAccErrorCheck(t, iam.EndpointsID), Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccAWSDataSourceIAMRolesConfig_basic(), + Config: testAccAWSIAMRolesConfigDataSource_basic, Check: resource.ComposeTestCheckFunc( - // testCheckResourceAttrIsNot(dataSourceName, "names.#", "0"), resource.TestMatchResourceAttr(dataSourceName, "names.#", regexp.MustCompile("[^0].*$")), ), }, @@ -29,17 +29,18 @@ func TestAccAWSDataSourceIAMRoles_basic(t *testing.T) { }) } -func TestAccAWSDataSourceIAMRoles_filterRoleName(t *testing.T) { +func TestAccAWSIAMRolesDataSource_nameRegex(t *testing.T) { rCount := strconv.Itoa(acctest.RandIntRange(1, 4)) rName := acctest.RandomWithPrefix("tf-acc-test") dataSourceName := "data.aws_iam_roles.test" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ErrorCheck: testAccErrorCheck(t, iam.EndpointsID), Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccAWSDataSourceIAMRolesConfig_filterByName(rCount, rName), + Config: testAccAWSIAMRolesConfigDataSource_nameRegex(rCount, rName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(dataSourceName, "names.#", rCount), resource.TestCheckResourceAttr(dataSourceName, "arns.#", rCount), @@ -49,18 +50,19 @@ func TestAccAWSDataSourceIAMRoles_filterRoleName(t *testing.T) { }) } -func TestAccAWSDataSourceIAMRoles_pathPrefix(t *testing.T) { +func TestAccAWSIAMRolesDataSource_pathPrefix(t *testing.T) { rCount := strconv.Itoa(acctest.RandIntRange(1, 4)) rName := acctest.RandomWithPrefix("tf-acc-test") rPathPrefix := acctest.RandomWithPrefix("tf-acc-path") dataSourceName := "data.aws_iam_roles.test" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ErrorCheck: testAccErrorCheck(t, iam.EndpointsID), Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccAWSDataSourceIAMRolesConfig_pathPrefix(rCount, rName, rPathPrefix), + Config: testAccAWSIAMRolesConfigDataSource_pathPrefix(rCount, rName, rPathPrefix), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(dataSourceName, "names.#", rCount), resource.TestCheckResourceAttr(dataSourceName, "arns.#", rCount), @@ -70,35 +72,54 @@ func TestAccAWSDataSourceIAMRoles_pathPrefix(t *testing.T) { }) } -func TestAccAWSDataSourceIAMRoles_pathPrefixAndFilterRoleName(t *testing.T) { +func TestAccAWSIAMRolesDataSource_nonExistentPathPrefix(t *testing.T) { + dataSourceName := "data.aws_iam_roles.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, iam.EndpointsID), + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAWSIAMRolesConfigDataSource_nonExistentPathPrefix, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "arns.#", "0"), + resource.TestCheckResourceAttr(dataSourceName, "names.#", "0"), + ), + }, + }, + }) +} + +func TestAccAWSIAMRolesDataSource_nameRegexAndPathPrefix(t *testing.T) { rCount := strconv.Itoa(acctest.RandIntRange(1, 4)) rName := acctest.RandomWithPrefix("tf-acc-test") rPathPrefix := acctest.RandomWithPrefix("tf-acc-path") dataSourceName := "data.aws_iam_roles.test" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ErrorCheck: testAccErrorCheck(t, iam.EndpointsID), Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccAWSDataSourceIAMRolesConfig_pathPrefixAndFilterRoleName(rCount, rName, rPathPrefix), + Config: testAccAWSIAMRolesConfigDataSource_nameRegexAndPathPrefix(rCount, rName, rPathPrefix, "0"), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(dataSourceName, "names.#", rCount), - resource.TestCheckResourceAttr(dataSourceName, "arns.#", rCount), + resource.TestCheckResourceAttr(dataSourceName, "names.#", "1"), + resource.TestCheckResourceAttr(dataSourceName, "arns.#", "1"), ), }, }, }) } -func testAccAWSDataSourceIAMRolesConfig_basic() string { - return ` +const testAccAWSIAMRolesConfigDataSource_basic = ` data "aws_iam_roles" "test" {} ` -} -func testAccAWSDataSourceIAMRolesConfig_filterByName(rCount string, rName string) string { +func testAccAWSIAMRolesConfigDataSource_nameRegex(rCount, rName string) string { return fmt.Sprintf(` +data "aws_partition" "current" {} resource "aws_iam_role" "test" { count = %[1]s @@ -111,7 +132,7 @@ resource "aws_iam_role" "test" { { "Action": "sts:AssumeRole", "Principal": { - "Service": "ec2.amazonaws.com" + "Service": "ec2.${data.aws_partition.current.dns_suffix}" }, "Effect": "Allow", "Sid": "" @@ -125,16 +146,14 @@ EOF } data "aws_iam_roles" "test" { - filter { - name = "role-name" - values = ["${aws_iam_role.test[0].tags["Seed"]}-*-role"] - } + name_regex = "${aws_iam_role.test[0].tags["Seed"]}-.*-role" } `, rCount, rName) } -func testAccAWSDataSourceIAMRolesConfig_pathPrefix(rCount string, rName string, rPathPrefix string) string { +func testAccAWSIAMRolesConfigDataSource_pathPrefix(rCount, rName, rPathPrefix string) string { return fmt.Sprintf(` +data "aws_partition" "current" {} resource "aws_iam_role" "test" { count = %[1]s @@ -147,7 +166,7 @@ resource "aws_iam_role" "test" { { "Action": "sts:AssumeRole", "Principal": { - "Service": "ec2.amazonaws.com" + "Service": "ec2.${data.aws_partition.current.dns_suffix}" }, "Effect": "Allow", "Sid": "" @@ -165,8 +184,15 @@ data "aws_iam_roles" "test" { `, rCount, rName, rPathPrefix) } -func testAccAWSDataSourceIAMRolesConfig_pathPrefixAndFilterRoleName(rCount string, rName string, rPathPrefix string) string { +const testAccAWSIAMRolesConfigDataSource_nonExistentPathPrefix = ` +data "aws_iam_roles" "test" { + path_prefix = "/dne/path" +} +` + +func testAccAWSIAMRolesConfigDataSource_nameRegexAndPathPrefix(rCount, rName, rPathPrefix, rIndex string) string { return fmt.Sprintf(` +data "aws_partition" "current" {} resource "aws_iam_role" "test" { count = %[1]s @@ -179,7 +205,7 @@ resource "aws_iam_role" "test" { { "Action": "sts:AssumeRole", "Principal": { - "Service": "ec2.amazonaws.com" + "Service": "ec2.${data.aws_partition.current.dns_suffix}" }, "Effect": "Allow", "Sid": "" @@ -195,11 +221,8 @@ EOF } data "aws_iam_roles" "test" { - filter { - name = "role-name" - values = ["${aws_iam_role.test[0].tags["Seed"]}-*-role", "*-role"] - } + name_regex = "${aws_iam_role.test[0].tags["Seed"]}-%[4]s-role" path_prefix = aws_iam_role.test[0].path } -`, rCount, rName, rPathPrefix) +`, rCount, rName, rPathPrefix, rIndex) } diff --git a/website/docs/d/iam_roles.html.markdown b/website/docs/d/iam_roles.html.markdown index 1c221dbeacc..f8d87c6e19b 100644 --- a/website/docs/d/iam_roles.html.markdown +++ b/website/docs/d/iam_roles.html.markdown @@ -8,30 +8,27 @@ description: |- # Data Source: aws_iam_roles -Use this data source to get ARNs and Names of IAM Roles that are created outside of the current Terraform state. +Use this data source to get the ARNs and Names of IAM Roles. ## Example Usage -### Retrieving all roles in an account +### All roles in an account ```terraform data "aws_iam_roles" "roles" {} ``` -### Retrieving roles by filter +### Roles filtered by name regex -Retrieving all IAM Roles whose role-name contains `project` +Roles whose role-name contains `project` ```terraform data "aws_iam_roles" "roles" { - filters = { - name = "role-name" - values = ["*project*"] - } + name_regex = ".*project.*" } ``` -### Retrieving roles by path prefix +### Roles filtered by path prefix ```terraform data "aws_iam_roles" "roles" { @@ -39,9 +36,9 @@ data "aws_iam_roles" "roles" { } ``` -### More examples +### Roles provisioned by AWS SSO -All IAM roles provisioned by AWS SSO in the account : +Roles in the account filtered by path prefix ```terraform data "aws_iam_roles" "roles" { @@ -49,34 +46,44 @@ data "aws_iam_roles" "roles" { } ``` -Specific IAM role provisioned by AWS SSO in the account : +Specific role in the account filtered by name regex and path prefix ```terraform data "aws_iam_roles" "roles" { - filters = { - name = "role-name" - values = ["AWSReservedSSO_permission_set_name_*"] - } + name_regex = "AWSReservedSSO_permission_set_name_.*" path_prefix = "/aws-reserved/sso.amazonaws.com/" } ``` -## Argument Reference +### Role ARNs with paths removed -The following arguments are supported: +For services like Amazon EKS that do not permit a path in the role ARN when used in a cluster's configuration map -* `filters` - (Optional) One or more name/value pairs to use as filters. Filter names and values are case-sensitive. If using multiple filters for rules, the results include IAM Roles for which any combination of rules - not necessarily a single rule - match all filters. +```terraform +data "aws_iam_roles" "roles" { + path_prefix = "/aws-reserved/sso.amazonaws.com/" +} - NOTICE: This filtering feature is not natively available in the [list-roles command of the AWS CLI][1]. Names of filters are based on the [Role structure][2] returned by the [list-roles command][1]. **Currently only the `role-name` filter is applicable.** +output "arns" { + value = [ + for parts in [for arn in data.aws_iam_roles.roles.arns : split("/", arn)] : + format("%s/%s", parts[0], element(parts, length(parts) - 1)) + ] +} +``` - `filters` are implemented using Glob matching as in EC2 APIs (See [Using Filtering](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Filtering.html#Filtering_Resources_CLI) section). +## Argument Reference + +The following arguments are supported: -* `path_prefix` - (Optional) The path prefix for filtering the results. For example, the prefix /application_abc/component_xyz/ gets all roles whose path starts with /application_abc/component_xyz/ . If it is not included, it defaults to a slash (/), listing all roles. For more details, check out [list-roles in the AWS CLI reference][1]. +* `name_regex` - (Optional) A regex string to apply to the IAM roles list returned by AWS. This allows more advanced filtering not supported from the AWS API. + This filtering is done locally on what AWS returns, and could have a performance impact if the result is large. It is recommended to combine this with other + options to narrow down the list AWS returns. +* `path_prefix` - (Optional) The path prefix for filtering the results. For example, the prefix `/application_abc/component_xyz/` gets all roles whose path starts with `/application_abc/component_xyz/`. If it is not included, it defaults to a slash (`/`), listing all roles. For more details, check out [list-roles in the AWS CLI reference][1]. ## Attributes Reference -* `arns` - ARNs of the matched IAM roles. -* `names` - Names of the matched IAM roles. +* `arns` - Set of ARNs of the matched IAM roles. +* `names` - Set of Names of the matched IAM roles. -[1]: https://docs.aws.amazon.com/cli/latest/reference/iam/list-roles.html -[2]: https://docs.aws.amazon.com/cli/latest/reference/iam/list-roles.html#output +[1]: https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/list-roles.html