Skip to content

Commit

Permalink
Merge pull request #41284 from GlennChia/f-aws_quicksight_data_source…
Browse files Browse the repository at this point in the history
…-param-s3-role_arn

r/aws_quicksight_data_source - parameters.s3.role_arn
  • Loading branch information
jar-b authored Feb 11, 2025
2 parents 71a0486 + 52b6abb commit 66408cd
Show file tree
Hide file tree
Showing 5 changed files with 360 additions and 2 deletions.
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

0 comments on commit 66408cd

Please sign in to comment.