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_quicksight_data_source - parameters.s3.role_arn #41284

Merged
3 changes: 3 additions & 0 deletions .changelog/41284.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_quicksight_data_source: Add `parameters.s3.role_arn` argument to allow override an account-wide role for a specific S3 data source
```
48 changes: 46 additions & 2 deletions internal/service/quicksight/data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package quicksight

import (
"context"
"errors"
"fmt"
"log"
"strings"
Expand All @@ -28,6 +29,15 @@ import (
"github.com/hashicorp/terraform-provider-aws/names"
)

const (
// Allow IAM role to become visible to the index
propagationTimeout = 2 * time.Minute

// accessDeniedExceptionMessage describes the error returned when the IAM role has not yet propagated
accessDeniedExceptionAssumeRoleMessage = "Failed to assume your role. Verify the trust relationships of the role in the IAM console"
accessDeniedExceptionInsufficientPermissionsMessage = "Insufficient permission to access the manifest file"
)

// @SDKResource("aws_quicksight_data_source", name="Data Source")
// @Tags(identifierAttribute="arn")
// @Testing(existsType="github.com/aws/aws-sdk-go-v2/service/quicksight/types;awstypes;awstypes.DataSource")
Expand Down Expand Up @@ -124,12 +134,29 @@ func resourceDataSourceCreate(ctx context.Context, d *schema.ResourceData, meta
input.VpcConnectionProperties = quicksightschema.ExpandVPCConnectionProperties(v.([]interface{}))
}

_, err := conn.CreateDataSource(ctx, input)
outputRaw, err := tfresource.RetryWhen(ctx, propagationTimeout,
func() (interface{}, error) {
return conn.CreateDataSource(ctx, input)
},
func(err error) (bool, error) {
var accessDeniedException *awstypes.AccessDeniedException

if errors.As(err, &accessDeniedException) && (strings.Contains(accessDeniedException.ErrorMessage(), accessDeniedExceptionAssumeRoleMessage) || strings.Contains(accessDeniedException.ErrorMessage(), accessDeniedExceptionInsufficientPermissionsMessage)) {
return true, err
}

return false, err
},
)

if err != nil {
return sdkdiag.AppendErrorf(diags, "creating QuickSight Data Source (%s): %s", id, err)
}

if outputRaw == nil {
return sdkdiag.AppendErrorf(diags, "creating QuickSight Data Source (%s): empty output", id)
}

d.SetId(id)

if _, err := waitDataSourceCreated(ctx, conn, awsAccountID, dataSourceID); err != nil {
Expand Down Expand Up @@ -220,12 +247,29 @@ func resourceDataSourceUpdate(ctx context.Context, d *schema.ResourceData, meta
input.VpcConnectionProperties = quicksightschema.ExpandVPCConnectionProperties(v.([]interface{}))
}

_, err = conn.UpdateDataSource(ctx, input)
outputRaw, err := tfresource.RetryWhen(ctx, propagationTimeout,
func() (interface{}, error) {
return conn.UpdateDataSource(ctx, input)
},
func(err error) (bool, error) {
var accessDeniedException *awstypes.AccessDeniedException

if errors.As(err, &accessDeniedException) && (strings.Contains(accessDeniedException.ErrorMessage(), accessDeniedExceptionAssumeRoleMessage) || strings.Contains(accessDeniedException.ErrorMessage(), accessDeniedExceptionInsufficientPermissionsMessage)) {
return true, err
}

return false, err
},
)

if err != nil {
return sdkdiag.AppendErrorf(diags, "updating QuickSight Data Source (%s): %s", d.Id(), err)
}

if outputRaw == nil {
return sdkdiag.AppendErrorf(diags, "updating QuickSight Data Source (%s): empty output", d.Id())
}

if _, err := waitDataSourceUpdated(ctx, conn, awsAccountID, dataSourceID); err != nil {
return sdkdiag.AppendErrorf(diags, "waiting for QuickSight Data Source (%s) update: %s", d.Id(), err)
}
Expand Down
202 changes: 202 additions & 0 deletions internal/service/quicksight/data_source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,58 @@ func TestAccQuickSightDataSource_secretARN(t *testing.T) {
})
}

func TestAccQuickSightDataSource_s3RoleARN(t *testing.T) {
ctx := acctest.Context(t)
var dataSource awstypes.DataSource
resourceName := "aws_quicksight_data_source.test"
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
rId := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
iamRoleResourceName := "aws_iam_role.test"
iamRoleResourceNameUpdated := "aws_iam_role.test2"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
ErrorCheck: acctest.ErrorCheck(t, names.QuickSightServiceID),
CheckDestroy: testAccCheckDataSourceDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccDataSourceConfig_s3RoleARN(rId, rName, rName2, iamRoleResourceName),
Check: resource.ComposeTestCheckFunc(
testAccCheckDataSourceExists(ctx, resourceName, &dataSource),
resource.TestCheckResourceAttr(resourceName, "data_source_id", rId),
resource.TestCheckResourceAttr(resourceName, "parameters.0.s3.#", "1"),
resource.TestCheckResourceAttr(resourceName, "parameters.0.s3.0.manifest_file_location.#", "1"),
resource.TestCheckResourceAttr(resourceName, "parameters.0.s3.0.manifest_file_location.0.bucket", rName),
resource.TestCheckResourceAttr(resourceName, "parameters.0.s3.0.manifest_file_location.0.key", rName),
resource.TestCheckResourceAttrPair(resourceName, "parameters.0.s3.0.role_arn", iamRoleResourceName, names.AttrARN),
resource.TestCheckResourceAttr(resourceName, names.AttrType, string(awstypes.DataSourceTypeS3)),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
// change the selector update the data source with the new Role
{
Config: testAccDataSourceConfig_s3RoleARN(rId, rName, rName2, iamRoleResourceNameUpdated),
Check: resource.ComposeTestCheckFunc(
testAccCheckDataSourceExists(ctx, resourceName, &dataSource),
resource.TestCheckResourceAttr(resourceName, "data_source_id", rId),
resource.TestCheckResourceAttr(resourceName, "parameters.0.s3.#", "1"),
resource.TestCheckResourceAttr(resourceName, "parameters.0.s3.0.manifest_file_location.#", "1"),
resource.TestCheckResourceAttr(resourceName, "parameters.0.s3.0.manifest_file_location.0.bucket", rName),
resource.TestCheckResourceAttr(resourceName, "parameters.0.s3.0.manifest_file_location.0.key", rName),
resource.TestCheckResourceAttrPair(resourceName, "parameters.0.s3.0.role_arn", iamRoleResourceNameUpdated, names.AttrARN),
resource.TestCheckResourceAttr(resourceName, names.AttrType, string(awstypes.DataSourceTypeS3)),
),
},
},
})
}

func testAccCheckDataSourceExists(ctx context.Context, n string, v *awstypes.DataSource) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
Expand Down Expand Up @@ -364,6 +416,57 @@ EOF
`, rName)
}

func testAccDataSourceConfig_baseNoACL(rName string) string {
return fmt.Sprintf(`
data "aws_partition" "current" {}
data "aws_region" "current" {}

resource "aws_s3_bucket" "test" {
bucket = %[1]q
force_destroy = true
}

resource "aws_s3_bucket_public_access_block" "test" {
bucket = aws_s3_bucket.test.id

block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}

resource "aws_s3_object" "test_data" {
bucket = aws_s3_bucket.test.bucket
key = "%[1]s-test-data.csv"
content = <<-EOT
name,sentiment
a,happy
b,happy
EOT
}

resource "aws_s3_object" "test" {
bucket = aws_s3_bucket.test.bucket
key = %[1]q
content = jsonencode({
fileLocations = [
{
URIs = [
"https://${aws_s3_bucket.test.id}.s3.${data.aws_region.current.name}.${data.aws_partition.current.dns_suffix}/%[1]s-test-data.csv"
]
}
]
globalUploadSettings = {
format = "CSV"
delimiter = ","
textqualifier = "\""
containsHeader = true
}
})
}
`, rName)
}

func testAccDataSourceConfig_basic(rId, rName string) string {
return acctest.ConfigCompose(
testAccDataSourceConfig_base(rName),
Expand Down Expand Up @@ -757,3 +860,102 @@ resource "aws_quicksight_data_source" "test" {
}
`, rId, rName)
}

func testAccDataSourceConfig_s3RoleARN(rId, rName, rName2, iamRoleResourceName string) string {
return acctest.ConfigCompose(
testAccDataSourceConfig_baseNoACL(rName),
fmt.Sprintf(`
data "aws_caller_identity" "current" {}

resource "aws_iam_role" "test" {
name = %[2]q

assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "quicksight.amazonaws.com"
}
Condition = {
StringEquals = {
"aws:SourceAccount" = data.aws_caller_identity.current.account_id
}
}
}
]
})
}

resource "aws_iam_role" "test2" {
name = %[3]q

assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "quicksight.amazonaws.com"
}
}
]
})
}

resource "aws_iam_policy" "test" {
name = %[2]q
description = "Policy to allow QuickSight access to S3 bucket"

policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = ["s3:GetObject"],
Effect = "Allow",
Resource = "${aws_s3_bucket.test.arn}/${aws_s3_object.test.key}"
},
{
Action = ["s3:ListBucket"],
Effect = "Allow",
Resource = aws_s3_bucket.test.arn
}
]
})
}

resource "aws_iam_role_policy_attachment" "test" {
policy_arn = aws_iam_policy.test.arn
role = aws_iam_role.test.name
}

resource "aws_iam_role_policy_attachment" "test2" {
policy_arn = aws_iam_policy.test.arn
role = aws_iam_role.test2.name
}

resource "aws_quicksight_data_source" "test" {
data_source_id = %[1]q
name = %[2]q

parameters {
s3 {
manifest_file_location {
bucket = aws_s3_bucket.test.bucket
key = aws_s3_object.test.key
}
role_arn = %[4]s.arn
}
}

type = "S3"

depends_on = [
aws_iam_role_policy_attachment.test
]
}
`, rId, rName, rName2, iamRoleResourceName))
}
10 changes: 10 additions & 0 deletions internal/service/quicksight/schema/data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,11 @@ func DataSourceParametersSchema() *schema.Schema {
},
},
},
names.AttrRoleARN: {
Type: schema.TypeString,
Optional: true,
ValidateFunc: verify.ValidARN,
},
},
},
ExactlyOneOf: exactlyOneOf,
Expand Down Expand Up @@ -906,6 +911,10 @@ func ExpandDataSourceParameters(tfList []interface{}) awstypes.DataSourceParamet
}
}

if v, ok := tfMap[names.AttrRoleARN].(string); ok && v != "" {
ps.Value.RoleArn = aws.String(v)
}

apiObject = ps
}
}
Expand Down Expand Up @@ -1130,6 +1139,7 @@ func FlattenDataSourceParameters(apiObject awstypes.DataSourceParameters) []inte
names.AttrKey: aws.ToString(v.Value.ManifestFileLocation.Key),
},
},
names.AttrRoleARN: aws.ToString(v.Value.RoleArn),
},
}
case *awstypes.DataSourceParametersMemberServiceNowParameters:
Expand Down
Loading
Loading