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

[New Function]: trim_iam_role_path #36723

Merged
merged 2 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all 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 .changelog/36723.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-function
trim_iam_role_path
```
92 changes: 92 additions & 0 deletions internal/function/trim_iam_role_path.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package function

import (
"context"
"fmt"
"strings"

"github.com/aws/aws-sdk-go-v2/aws/arn"
"github.com/hashicorp/terraform-plugin-framework/function"
)

const (
// IAM role ARN reference:
// https://docs.aws.amazon.com/IAM/latest/UserGuide/list_awsidentityandaccessmanagementiam.html#awsidentityandaccessmanagementiam-resources-for-iam-policies

// resourceSectionPrefix is the expected prefix in the resource section of
// an IAM role ARN
resourceSectionPrefix = "role/"

// serviceSection is the expected service section of an IAM role ARN
serviceSection = "iam"
)

var _ function.Function = trimIAMRolePathFunction{}

func NewTrimIAMRolePathFunction() function.Function {
return &trimIAMRolePathFunction{}
}

type trimIAMRolePathFunction struct{}

func (f trimIAMRolePathFunction) Metadata(ctx context.Context, req function.MetadataRequest, resp *function.MetadataResponse) {
resp.Name = "trim_iam_role_path"
}

func (f trimIAMRolePathFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) {
resp.Definition = function.Definition{
Summary: "trim_iam_role_path Function",
MarkdownDescription: "Trims the path prefix from an IAM role Amazon Resource Name (ARN). This " +
"function can be used when services require role ARNs to be passed without a path.",
Parameters: []function.Parameter{
function.StringParameter{
Name: "arn",
MarkdownDescription: "IAM role Amazon Resource Name (ARN)",
},
},
Return: function.StringReturn{},
}
}

func (f trimIAMRolePathFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) {
var arg string

resp.Error = function.ConcatFuncErrors(req.Arguments.Get(ctx, &arg))
if resp.Error != nil {
return
}

result, err := trimPath(arg)
if err != nil {
resp.Error = function.ConcatFuncErrors(resp.Error, function.NewFuncError(err.Error()))
return
}

resp.Error = function.ConcatFuncErrors(resp.Result.Set(ctx, result))
}

// trimPath removes all path prefixes from the resource section of a role ARN
func trimPath(s string) (string, error) {
rarn, err := arn.Parse(s)
if err != nil {
return "", err
}

if rarn.Service != serviceSection {
return "", fmt.Errorf(`service must be "%s"`, serviceSection)
}
if rarn.Region != "" {
return "", fmt.Errorf("region must be empty")
}
if !strings.HasPrefix(rarn.Resource, resourceSectionPrefix) {
return "", fmt.Errorf(`resource must begin with "%s"`, resourceSectionPrefix)
}

sec := strings.Split(rarn.Resource, "/")
rarn.Resource = fmt.Sprintf("%s%s", resourceSectionPrefix, sec[len(sec)-1])

return rarn.String(), nil
}
146 changes: 146 additions & 0 deletions internal/function/trim_iam_role_path_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package function_test

import (
"fmt"
"testing"

"github.com/YakDriver/regexache"
"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/tfversion"
"github.com/hashicorp/terraform-provider-aws/internal/acctest"
)

var (
// ExpectError parses the human readable output of a terraform apply run, in which
// formatting (including line breaks) may change over time. For extra safety, we add
// optional whitespace between each word in the expected error text.

expectedErrorInvalidARN = regexache.MustCompile(`invalid[\s\n]*prefix`)
expectedErrorInvalidService = regexache.MustCompile(`service[\s\n]*must`)
expectedErrorInvalidRegion = regexache.MustCompile(`region[\s\n]*must`)
expectedErrorInvalidResource = regexache.MustCompile(`resource[\s\n]*must`)
)

func TestTrimIAMRolePathFunction_valid(t *testing.T) {
t.Parallel()
arg := "arn:aws:iam::444455556666:role/example"

resource.UnitTest(t, resource.TestCase{
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(version.Must(version.NewVersion("1.8.0-beta1"))),
},
Steps: []resource.TestStep{
{
Config: testTrimIAMRolePathFunctionConfig(arg),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckOutput("test", arg),
),
},
},
})
}

func TestTrimIAMRolePathFunction_validWithPath(t *testing.T) {
t.Parallel()
arg := "arn:aws:iam::444455556666:role/with/some/path/parts/example"
expected := "arn:aws:iam::444455556666:role/example"

resource.UnitTest(t, resource.TestCase{
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(version.Must(version.NewVersion("1.8.0-beta1"))),
},
Steps: []resource.TestStep{
{
Config: testTrimIAMRolePathFunctionConfig(arg),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckOutput("test", expected),
),
},
},
})
}

func TestTrimIAMRolePathFunction_invalidARN(t *testing.T) {
t.Parallel()
arg := "foo"

resource.UnitTest(t, resource.TestCase{
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(version.Must(version.NewVersion("1.8.0-beta1"))),
},
Steps: []resource.TestStep{
{
Config: testTrimIAMRolePathFunctionConfig(arg),
ExpectError: expectedErrorInvalidARN,
},
},
})
}

func TestTrimIAMRolePathFunction_invalidService(t *testing.T) {
t.Parallel()
arg := "arn:aws:s3:::bucket/foo"

resource.UnitTest(t, resource.TestCase{
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(version.Must(version.NewVersion("1.8.0-beta1"))),
},
Steps: []resource.TestStep{
{
Config: testTrimIAMRolePathFunctionConfig(arg),
ExpectError: expectedErrorInvalidService,
},
},
})
}

func TestTrimIAMRolePathFunction_invalidRegion(t *testing.T) {
t.Parallel()
arg := "arn:aws:iam:us-east-1:444455556666:role/example"

resource.UnitTest(t, resource.TestCase{
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(version.Must(version.NewVersion("1.8.0-beta1"))),
},
Steps: []resource.TestStep{
{
Config: testTrimIAMRolePathFunctionConfig(arg),
ExpectError: expectedErrorInvalidRegion,
},
},
})
}

func TestTrimIAMRolePathFunction_invalidResource(t *testing.T) {
t.Parallel()
arg := "arn:aws:iam::444455556666:policy/example"

resource.UnitTest(t, resource.TestCase{
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(version.Must(version.NewVersion("1.8.0-beta1"))),
},
Steps: []resource.TestStep{
{
Config: testTrimIAMRolePathFunctionConfig(arg),
ExpectError: expectedErrorInvalidResource,
},
},
})
}

func testTrimIAMRolePathFunctionConfig(arg string) string {
return fmt.Sprintf(`
output "test" {
value = provider::aws::trim_iam_role_path(%[1]q)
}`, arg)
}
1 change: 1 addition & 0 deletions internal/provider/fwprovider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ func (p *fwprovider) Functions(_ context.Context) []func() function.Function {
return []func() function.Function{
tffunction.NewARNBuildFunction,
tffunction.NewARNParseFunction,
tffunction.NewTrimIAMRolePathFunction,
}
}

Expand Down
35 changes: 35 additions & 0 deletions website/docs/functions/trim_iam_role_path.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
subcategory: ""
layout: "aws"
page_title: "AWS: trim_iam_role_path"
description: |-
Trims the path prefix from an IAM role Amazon Resource Name (ARN).
---

# Function: trim_iam_role_path

~> Provider-defined function support is in technical preview and offered without compatibility promises until Terraform 1.8 is generally available.

Trims the path prefix from an IAM role Amazon Resource Name (ARN).
This function can be used when services require role ARNs to be passed without a path.

See the [AWS IAM documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/list_awsidentityandaccessmanagementiam.html#awsidentityandaccessmanagementiam-resources-for-iam-policies) for additional information on IAM role ARNs.

## Example Usage

```terraform
# result: arn:aws:iam::444455556666:role/example
output "example" {
value = provider::aws::trim_iam_role_path("arn:aws:iam::444455556666:role/with/path/example")
}
```

## Signature

```text
trim_iam_role_path(arn string) string
```

## Arguments

1. `arn` (String) IAM role Amazon Resource Name (ARN).
Loading