From 1d7cbf278f438c81fc989d534b647c3f1c9b7172 Mon Sep 17 00:00:00 2001 From: Luke Cernetic Date: Wed, 25 Aug 2021 13:14:16 -0700 Subject: [PATCH 01/12] added code from @mjgpy3 --- aws/provider.go | 1 + aws/resource_aws_quicksight_data_source.go | 1320 +++++++++++++++++ aws/structure.go | 115 ++ aws/structure_test.go | 223 +++ aws/tagsQuickSight.go | 46 + .../r/quicksight_data_source.html.markdown | 224 +++ 6 files changed, 1929 insertions(+) create mode 100644 aws/resource_aws_quicksight_data_source.go create mode 100644 aws/tagsQuickSight.go create mode 100644 website/docs/r/quicksight_data_source.html.markdown diff --git a/aws/provider.go b/aws/provider.go index ae7054ac902d..33edd595fdae 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -963,6 +963,7 @@ func Provider() *schema.Provider { "aws_prometheus_workspace": resourceAwsPrometheusWorkspace(), "aws_proxy_protocol_policy": resourceAwsProxyProtocolPolicy(), "aws_qldb_ledger": resourceAwsQLDBLedger(), + "aws_quicksight_data_source": resourceAwsQuickSightDataSource(), "aws_quicksight_group": resourceAwsQuickSightGroup(), "aws_quicksight_user": resourceAwsQuickSightUser(), "aws_ram_principal_association": resourceAwsRamPrincipalAssociation(), diff --git a/aws/resource_aws_quicksight_data_source.go b/aws/resource_aws_quicksight_data_source.go new file mode 100644 index 000000000000..741834891da8 --- /dev/null +++ b/aws/resource_aws_quicksight_data_source.go @@ -0,0 +1,1320 @@ +package aws + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/quicksight" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceAwsQuickSightDataSource() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsQuickSightDataSourceCreate, + Read: resourceAwsQuickSightDataSourceRead, + Update: resourceAwsQuickSightDataSourceUpdate, + Delete: resourceAwsQuickSightDataSourceDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + + "aws_account_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "credentials": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "credential_pair": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "password": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "username": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + }, + }, + }, + }, + }, + }, + + "data_source_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + + "parameters": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + MinItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "amazon_elasticsearch": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "domain": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + }, + }, + }, + "athena": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "work_group": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.NoZeroValues, + }, + }, + }, + }, + "aurora": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "host": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "port": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + }, + }, + }, + "aurora_postgresql": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "host": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "port": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + }, + }, + }, + "aws_iot_analytics": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "data_set_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + }, + }, + }, + "jira": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "site_base_url": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + }, + }, + }, + "maria_db": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "host": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "port": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + }, + }, + }, + "mysql": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "host": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "port": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + }, + }, + }, + "postgresql": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "host": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "port": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + }, + }, + }, + "presto": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "catalog": { + Type: schema.TypeString, + Required: true, + }, + "host": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "port": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + }, + }, + }, + // The documentation is not clear on how to pass RDS parameters... + "redshift": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cluster_id": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.NoZeroValues, + }, + "database": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "host": { + Type: schema.TypeString, + Optional: true, + }, + "port": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, + "s3": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "manifest_file_location": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "bucket": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + }, + }, + }, + }, + }, + }, + "service_now": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "site_base_url": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + }, + }, + }, + "snowflake": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "host": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "warehouse": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "spark": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "host": { + Type: schema.TypeString, + Required: true, + }, + "port": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + }, + }, + }, + "sql_server": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "host": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "port": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + }, + }, + }, + "teradata": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "host": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "port": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + }, + }, + }, + "twitter": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max_rows": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + "query": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + }, + }, + }, + }, + }, + }, + + "permission": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "actions": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + "principal": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + }, + }, + }, + + "ssl_properties": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "disable_ssl": { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + + "tags": tagsSchema(), + + // This will be inferred from the passed `parameters` value + "type": { + Type: schema.TypeString, + Computed: true, + }, + + "vpc_connection_properties": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "vpc_connection_arn": { + Type: schema.TypeBool, + Optional: true, + ValidateFunc: validateArn, + }, + }, + }, + }, + }, + } +} + +func resourceAwsQuickSightDataSourceCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).quicksightconn + + awsAccountId := meta.(*AWSClient).accountid + id := d.Get("data_source_id").(string) + + if v, ok := d.GetOk("aws_account_id"); ok { + awsAccountId = v.(string) + } + + params := &quicksight.CreateDataSourceInput{ + AwsAccountId: aws.String(awsAccountId), + DataSourceId: aws.String(id), + Name: aws.String(d.Get("name").(string)), + } + + if credentials := resourceAwsQuickSightDataSourceCredentials(d); credentials != nil { + params.Credentials = credentials + } + + if dataSourceType, dataSourceParameters := resourceAwsQuickSightDataSourceParameters(d); dataSourceParameters != nil { + params.Type = dataSourceType + params.DataSourceParameters = dataSourceParameters + } + + if v := d.Get("permission"); v != nil && len(v.([]interface{})) != 0 { + params.Permissions = make([]*quicksight.ResourcePermission, 0) + + for _, v := range v.([]interface{}) { + permissionResource := v.(map[string]interface{}) + permission := &quicksight.ResourcePermission{ + Actions: expandStringSet(permissionResource["actions"].(*schema.Set)), + Principal: aws.String(permissionResource["principal"].(string)), + } + + params.Permissions = append(params.Permissions, permission) + } + } + + if sslProperties := resourceAwsQuickSightDataSourceSslProperties(d); sslProperties != nil { + params.SslProperties = sslProperties + } + + if v, ok := d.GetOk("tags"); ok { + params.Tags = tagsFromMapQuickSight(v.(map[string]interface{})) + } + + if vpcConnectionProperties := resourceAwsQuickSightDataSourceVpcConnectionProperties(d); vpcConnectionProperties != nil { + params.VpcConnectionProperties = vpcConnectionProperties + } + + _, err := conn.CreateDataSource(params) + if err != nil { + return fmt.Errorf("Error creating QuickSight Data Source: %s", err) + } + + d.SetId(fmt.Sprintf("%s/%s", awsAccountId, id)) + + return resourceAwsQuickSightDataSourceRead(d, meta) +} + +func resourceAwsQuickSightDataSourceRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).quicksightconn + + awsAccountId, dataSourceId, err := resourceAwsQuickSightDataSourceParseID(d.Id()) + if err != nil { + return err + } + + descOpts := &quicksight.DescribeDataSourceInput{ + AwsAccountId: aws.String(awsAccountId), + DataSourceId: aws.String(dataSourceId), + } + + var dataSourceResp *quicksight.DescribeDataSourceOutput + err = resource.Retry(5*time.Minute, func() *resource.RetryError { + var err error + dataSourceResp, err = conn.DescribeDataSource(descOpts) + + if dataSourceResp != nil && dataSourceResp.DataSource != nil { + status := aws.StringValue(dataSourceResp.DataSource.Status) + + if status == quicksight.ResourceStatusCreationInProgress || status == quicksight.ResourceStatusUpdateInProgress { + return resource.RetryableError(fmt.Errorf("Data Source operation still in progress (%s): %s", d.Id(), status)) + } + if status == quicksight.ResourceStatusCreationFailed || status == quicksight.ResourceStatusUpdateFailed { + return resource.NonRetryableError(fmt.Errorf("Data Source operation failed (%s): %s", d.Id(), status)) + } + } + + if err != nil { + return resource.NonRetryableError(err) + } + + return nil + }) + + if isAWSErr(err, quicksight.ErrCodeResourceNotFoundException, "") { + log.Printf("[WARN] QuickSight Data Source %s is already gone", d.Id()) + d.SetId("") + return nil + } + if err != nil { + return fmt.Errorf("Error describing QuickSight Data Source (%s): %s", d.Id(), err) + } + + permsResp, err := conn.DescribeDataSourcePermissions(&quicksight.DescribeDataSourcePermissionsInput{ + AwsAccountId: aws.String(awsAccountId), + DataSourceId: aws.String(dataSourceId), + }) + + if err != nil { + return fmt.Errorf("Error describing QuickSight Data Source permissions (%s): %s", d.Id(), err) + } + + dataSource := dataSourceResp.DataSource + + d.Set("arn", dataSource.Arn) + d.Set("name", dataSource.Name) + d.Set("data_source_id", dataSource.DataSourceId) + d.Set("aws_account_id", awsAccountId) + + if err := d.Set("permission", flattenQuickSightPermissions(permsResp.Permissions)); err != nil { + return fmt.Errorf("Error setting permission error: %#v", err) + } + + params := map[string]interface{}{} + + if dataSource.DataSourceParameters.AmazonElasticsearchParameters != nil { + params = map[string]interface{}{ + "amazon_elasticsearch": []interface{}{ + map[string]interface{}{ + "domain": dataSource.DataSourceParameters.AmazonElasticsearchParameters.Domain, + }, + }, + } + } + + if dataSource.DataSourceParameters.AthenaParameters != nil { + params = map[string]interface{}{ + "athena": []interface{}{ + map[string]interface{}{ + "work_group": dataSource.DataSourceParameters.AthenaParameters.WorkGroup, + }, + }, + } + } + + if dataSource.DataSourceParameters.AuroraParameters != nil { + params = map[string]interface{}{ + "aurora": []interface{}{ + map[string]interface{}{ + "database": dataSource.DataSourceParameters.AuroraParameters.Database, + "host": dataSource.DataSourceParameters.AuroraParameters.Host, + "port": dataSource.DataSourceParameters.AuroraParameters.Port, + }, + }, + } + } + + if dataSource.DataSourceParameters.AuroraPostgreSqlParameters != nil { + params = map[string]interface{}{ + "aurora_postgresql": []interface{}{ + map[string]interface{}{ + "database": dataSource.DataSourceParameters.AuroraPostgreSqlParameters.Database, + "host": dataSource.DataSourceParameters.AuroraPostgreSqlParameters.Host, + "port": dataSource.DataSourceParameters.AuroraPostgreSqlParameters.Port, + }, + }, + } + } + + if dataSource.DataSourceParameters.AwsIotAnalyticsParameters != nil { + params = map[string]interface{}{ + "aws_iot_analytics": []interface{}{ + map[string]interface{}{ + "data_set_name": dataSource.DataSourceParameters.AwsIotAnalyticsParameters.DataSetName, + }, + }, + } + } + + if dataSource.DataSourceParameters.JiraParameters != nil { + params = map[string]interface{}{ + "jira": []interface{}{ + map[string]interface{}{ + "site_base_url": dataSource.DataSourceParameters.JiraParameters.SiteBaseUrl, + }, + }, + } + } + + if dataSource.DataSourceParameters.MariaDbParameters != nil { + params = map[string]interface{}{ + "maria_db": []interface{}{ + map[string]interface{}{ + "database": dataSource.DataSourceParameters.MariaDbParameters.Database, + "host": dataSource.DataSourceParameters.MariaDbParameters.Host, + "port": dataSource.DataSourceParameters.MariaDbParameters.Port, + }, + }, + } + } + + if dataSource.DataSourceParameters.MySqlParameters != nil { + params = map[string]interface{}{ + "mysql": []interface{}{ + map[string]interface{}{ + "database": dataSource.DataSourceParameters.MySqlParameters.Database, + "host": dataSource.DataSourceParameters.MySqlParameters.Host, + "port": dataSource.DataSourceParameters.MySqlParameters.Port, + }, + }, + } + } + + if dataSource.DataSourceParameters.PostgreSqlParameters != nil { + params = map[string]interface{}{ + "postgresql": []interface{}{ + map[string]interface{}{ + "database": dataSource.DataSourceParameters.PostgreSqlParameters.Database, + "host": dataSource.DataSourceParameters.PostgreSqlParameters.Host, + "port": dataSource.DataSourceParameters.PostgreSqlParameters.Port, + }, + }, + } + } + + if dataSource.DataSourceParameters.PrestoParameters != nil { + params = map[string]interface{}{ + "presto": []interface{}{ + map[string]interface{}{ + "catalog": dataSource.DataSourceParameters.PrestoParameters.Catalog, + "host": dataSource.DataSourceParameters.PrestoParameters.Host, + "port": dataSource.DataSourceParameters.PrestoParameters.Port, + }, + }, + } + } + + if dataSource.DataSourceParameters.RedshiftParameters != nil { + params = map[string]interface{}{ + "redshift": []interface{}{ + map[string]interface{}{ + "cluster_id": dataSource.DataSourceParameters.RedshiftParameters.ClusterId, + "database": dataSource.DataSourceParameters.RedshiftParameters.Database, + "host": dataSource.DataSourceParameters.RedshiftParameters.Host, + "port": dataSource.DataSourceParameters.RedshiftParameters.Port, + }, + }, + } + } + + if dataSource.DataSourceParameters.S3Parameters != nil { + params = map[string]interface{}{ + "s3": []interface{}{ + map[string]interface{}{ + "manifest_file_location": []interface{}{ + map[string]interface{}{ + "bucket": dataSource.DataSourceParameters.S3Parameters.ManifestFileLocation.Bucket, + "key": dataSource.DataSourceParameters.S3Parameters.ManifestFileLocation.Key, + }, + }, + }, + }, + } + } + + if dataSource.DataSourceParameters.ServiceNowParameters != nil { + params = map[string]interface{}{ + "service_now": []interface{}{ + map[string]interface{}{ + "site_base_url": dataSource.DataSourceParameters.ServiceNowParameters.SiteBaseUrl, + }, + }, + } + } + + if dataSource.DataSourceParameters.SnowflakeParameters != nil { + params = map[string]interface{}{ + "snowflake": []interface{}{ + map[string]interface{}{ + "database": dataSource.DataSourceParameters.SnowflakeParameters.Database, + "host": dataSource.DataSourceParameters.SnowflakeParameters.Host, + "warehouse": dataSource.DataSourceParameters.SnowflakeParameters.Warehouse, + }, + }, + } + } + + if dataSource.DataSourceParameters.SparkParameters != nil { + params = map[string]interface{}{ + "spark": []interface{}{ + map[string]interface{}{ + "host": dataSource.DataSourceParameters.SparkParameters.Host, + "port": dataSource.DataSourceParameters.SparkParameters.Port, + }, + }, + } + } + + if dataSource.DataSourceParameters.SqlServerParameters != nil { + params = map[string]interface{}{ + "sql_server": []interface{}{ + map[string]interface{}{ + "database": dataSource.DataSourceParameters.SqlServerParameters.Database, + "host": dataSource.DataSourceParameters.SqlServerParameters.Host, + "port": dataSource.DataSourceParameters.SqlServerParameters.Port, + }, + }, + } + } + + if dataSource.DataSourceParameters.TeradataParameters != nil { + params = map[string]interface{}{ + "teradata": []interface{}{ + map[string]interface{}{ + "database": dataSource.DataSourceParameters.TeradataParameters.Database, + "host": dataSource.DataSourceParameters.TeradataParameters.Host, + "port": dataSource.DataSourceParameters.TeradataParameters.Port, + }, + }, + } + } + + if dataSource.DataSourceParameters.TwitterParameters != nil { + params = map[string]interface{}{ + "twitter": []interface{}{ + map[string]interface{}{ + "max_rows": dataSource.DataSourceParameters.TwitterParameters.MaxRows, + "query": dataSource.DataSourceParameters.TwitterParameters.Query, + }, + }, + } + } + + d.Set("parameters", []interface{}{params}) + + d.Set("type", inferQuickSightDataSourceTypeFromKey(params)) + + return nil +} + +func resourceAwsQuickSightDataSourceUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).quicksightconn + + awsAccountId, dataSourceId, err := resourceAwsQuickSightDataSourceParseID(d.Id()) + if err != nil { + return err + } + + params := &quicksight.UpdateDataSourceInput{ + AwsAccountId: aws.String(awsAccountId), + DataSourceId: aws.String(dataSourceId), + } + + if credentials := resourceAwsQuickSightDataSourceCredentials(d); credentials != nil { + params.Credentials = credentials + } + + if dataSourceType, dataSourceParameters := resourceAwsQuickSightDataSourceParameters(d); dataSourceParameters != nil { + params.DataSourceParameters = dataSourceParameters + d.Set("type", dataSourceType) + } + + if d.HasChange("permission") { + oraw, nraw := d.GetChange("permission") + o := oraw.([]interface{}) + n := nraw.([]interface{}) + toGrant, toRevoke := diffQuickSightPermissionsToGrantAndRevoke(o, n) + + if len(toGrant) > 0 || len(toRevoke) > 0 { + params := &quicksight.UpdateDataSourcePermissionsInput{ + AwsAccountId: aws.String(awsAccountId), + DataSourceId: aws.String(dataSourceId), + GrantPermissions: toGrant, + RevokePermissions: toRevoke, + } + + _, err := conn.UpdateDataSourcePermissions(params) + if err != nil { + return fmt.Errorf("Error updating QuickSight Data Source permissions: %s", err) + } + } + } + + if sslProperties := resourceAwsQuickSightDataSourceSslProperties(d); sslProperties != nil { + params.SslProperties = sslProperties + } + + if d.HasChange("tags") { + oraw, nraw := d.GetChange("tags") + o := oraw.(map[string]interface{}) + n := nraw.(map[string]interface{}) + c, r := diffTagsQuickSight(tagsFromMapQuickSight(o), tagsFromMapQuickSight(n)) + + if len(r) > 0 { + _, err := conn.UntagResource(&quicksight.UntagResourceInput{ + ResourceArn: aws.String(quicksightDataSourceArn(meta.(*AWSClient).region, awsAccountId, dataSourceId)), + TagKeys: tagKeysQuickSight(r), + }) + if err != nil { + return fmt.Errorf("Error deleting QuickSight Data Source tags: %s", err) + } + } + + if len(c) > 0 { + _, err := conn.TagResource(&quicksight.TagResourceInput{ + ResourceArn: aws.String(quicksightDataSourceArn(meta.(*AWSClient).region, awsAccountId, dataSourceId)), + Tags: c, + }) + if err != nil { + return fmt.Errorf("Error updating QuickSight Data Source tags: %s", err) + } + } + } + + if vpcConnectionProperties := resourceAwsQuickSightDataSourceVpcConnectionProperties(d); vpcConnectionProperties != nil { + params.VpcConnectionProperties = vpcConnectionProperties + } + + _, err = conn.UpdateDataSource(params) + if isAWSErr(err, quicksight.ErrCodeResourceNotFoundException, "") { + log.Printf("[WARN] QuickSight Data Source %s is already gone", d.Id()) + d.SetId("") + return nil + } + if err != nil { + return fmt.Errorf("Error updating QuickSight Data Source %s: %s", d.Id(), err) + } + + return resourceAwsQuickSightDataSourceRead(d, meta) +} + +func resourceAwsQuickSightDataSourceDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).quicksightconn + + awsAccountId, dataSourceId, err := resourceAwsQuickSightDataSourceParseID(d.Id()) + if err != nil { + return err + } + + deleteOpts := &quicksight.DeleteDataSourceInput{ + AwsAccountId: aws.String(awsAccountId), + DataSourceId: aws.String(dataSourceId), + } + + if _, err := conn.DeleteDataSource(deleteOpts); err != nil { + if isAWSErr(err, quicksight.ErrCodeResourceNotFoundException, "") { + return nil + } + return fmt.Errorf("Error deleting QuickSight Data Source %s: %s", d.Id(), err) + } + + return nil +} + +func resourceAwsQuickSightDataSourceCredentials(d *schema.ResourceData) *quicksight.DataSourceCredentials { + if v := d.Get("credentials"); v != nil { + for _, v := range v.([]interface{}) { + credentials := v.(map[string]interface{}) + + if v := credentials["credential_pair"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + credentialPairResource := v.(map[string]interface{}) + credentialPair := &quicksight.CredentialPair{} + + if v, ok := credentialPairResource["username"]; ok && v.(string) != "" { + credentialPair.Username = aws.String(v.(string)) + } + + if v, ok := credentialPairResource["password"]; ok && v.(string) != "" { + credentialPair.Password = aws.String(v.(string)) + } + + return &quicksight.DataSourceCredentials{ + CredentialPair: credentialPair, + } + } + } + } + } + + return nil +} + +var quickSightDataSourceParamToDataType = map[string]string{ + "amazon_elasticsearch": quicksight.DataSourceTypeAmazonElasticsearch, + "athena": quicksight.DataSourceTypeAthena, + "aurora": quicksight.DataSourceTypeAurora, + "aurora_postgresql": quicksight.DataSourceTypeAuroraPostgresql, + "aws_iot_analytics": quicksight.DataSourceTypeAwsIotAnalytics, + "jira": quicksight.DataSourceTypeJira, + "maria_db": quicksight.DataSourceTypeMariadb, + "mysql": quicksight.DataSourceTypeMysql, + "postgresql": quicksight.DataSourceTypePostgresql, + "presto": quicksight.DataSourceTypePresto, + "redshift": quicksight.DataSourceTypeRedshift, + "s3": quicksight.DataSourceTypeS3, + "service_now": quicksight.DataSourceTypeServicenow, + "snowflake": quicksight.DataSourceTypeSnowflake, + "spark": quicksight.DataSourceTypeSpark, + "sql_server": quicksight.DataSourceTypeSqlserver, + "teradata": quicksight.DataSourceTypeTeradata, + "twitter": quicksight.DataSourceTypeTwitter, +} + +func inferQuickSightDataSourceTypeFromKey(params map[string]interface{}) string { + if len(params) == 1 { + for k := range params { + if dataSourceType, found := quickSightDataSourceParamToDataType[k]; found { + return dataSourceType + } + } + } + + for k, v := range params { + if dataSourceType, found := quickSightDataSourceParamToDataType[k]; found && v.([]interface{}) != nil && len(v.([]interface{})) > 0 { + return dataSourceType + } + } + + return "UNKNOWN" +} + +func resourceAwsQuickSightDataSourceParameters(d *schema.ResourceData) (*string, *quicksight.DataSourceParameters) { + if v := d.Get("parameters"); v != nil { + dataSourceParamsResource := &quicksight.DataSourceParameters{} + var dataSourceType string + for _, v := range v.([]interface{}) { + dataSourceParams := v.(map[string]interface{}) + dataSourceType = inferQuickSightDataSourceTypeFromKey(dataSourceParams) + + if v := dataSourceParams["amazon_elasticsearch"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + psResource := v.(map[string]interface{}) + dataSourceParamsResource.AmazonElasticsearchParameters = &quicksight.AmazonElasticsearchParameters{ + Domain: aws.String(psResource["domain"].(string)), + } + } + } + + if v := dataSourceParams["athena"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + psResource := v.(map[string]interface{}) + ps := &quicksight.AthenaParameters{} + + if v, ok := psResource["work_group"]; ok && v.(string) != "" { + ps.WorkGroup = aws.String(v.(string)) + } + + dataSourceParamsResource.AthenaParameters = ps + } + } + + if v := dataSourceParams["aurora"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + psResource := v.(map[string]interface{}) + dataSourceParamsResource.AuroraParameters = &quicksight.AuroraParameters{ + Database: aws.String(psResource["database"].(string)), + Host: aws.String(psResource["host"].(string)), + Port: aws.Int64(psResource["port"].(int64)), + } + } + } + + if v := dataSourceParams["aurora_postgresql"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + psResource := v.(map[string]interface{}) + dataSourceParamsResource.AuroraPostgreSqlParameters = &quicksight.AuroraPostgreSqlParameters{ + Database: aws.String(psResource["database"].(string)), + Host: aws.String(psResource["host"].(string)), + Port: aws.Int64(psResource["port"].(int64)), + } + } + } + + if v := dataSourceParams["aws_iot_analytics"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + psResource := v.(map[string]interface{}) + dataSourceParamsResource.AwsIotAnalyticsParameters = &quicksight.AwsIotAnalyticsParameters{ + DataSetName: aws.String(psResource["data_set_name"].(string)), + } + } + } + + if v := dataSourceParams["jira"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + psResource := v.(map[string]interface{}) + dataSourceParamsResource.JiraParameters = &quicksight.JiraParameters{ + SiteBaseUrl: aws.String(psResource["site_base_url"].(string)), + } + } + } + + if v := dataSourceParams["maria_db"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + psResource := v.(map[string]interface{}) + dataSourceParamsResource.MariaDbParameters = &quicksight.MariaDbParameters{ + Database: aws.String(psResource["database"].(string)), + Host: aws.String(psResource["host"].(string)), + Port: aws.Int64(psResource["port"].(int64)), + } + } + } + + if v := dataSourceParams["mysql"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + psResource := v.(map[string]interface{}) + dataSourceParamsResource.MySqlParameters = &quicksight.MySqlParameters{ + Database: aws.String(psResource["database"].(string)), + Host: aws.String(psResource["host"].(string)), + Port: aws.Int64(psResource["port"].(int64)), + } + } + } + + if v := dataSourceParams["postgresql"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + psResource := v.(map[string]interface{}) + dataSourceParamsResource.PostgreSqlParameters = &quicksight.PostgreSqlParameters{ + Database: aws.String(psResource["database"].(string)), + Host: aws.String(psResource["host"].(string)), + Port: aws.Int64(psResource["port"].(int64)), + } + } + } + + if v := dataSourceParams["presto"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + psResource := v.(map[string]interface{}) + dataSourceParamsResource.PrestoParameters = &quicksight.PrestoParameters{ + Catalog: aws.String(psResource["catalog"].(string)), + Host: aws.String(psResource["host"].(string)), + Port: aws.Int64(psResource["port"].(int64)), + } + } + } + + if v := dataSourceParams["redshift"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + psResource := v.(map[string]interface{}) + ps := &quicksight.RedshiftParameters{ + Database: aws.String(psResource["database"].(string)), + } + + if v, ok := psResource["cluster_id"]; ok && v.(string) != "" { + ps.ClusterId = aws.String(v.(string)) + } + + if v, ok := psResource["host"]; ok && v.(string) != "" { + ps.Host = aws.String(v.(string)) + } + + if v, ok := psResource["port"]; ok && v.(int64) != 0 { + ps.Port = aws.Int64(v.(int64)) + } + + dataSourceParamsResource.RedshiftParameters = ps + } + } + + if v := dataSourceParams["s3"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + s3 := v.(map[string]interface{}) + if v := s3["manifest_file_location"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + psResource := v.(map[string]interface{}) + dataSourceParamsResource.S3Parameters = &quicksight.S3Parameters{ + ManifestFileLocation: &quicksight.ManifestFileLocation{ + Bucket: aws.String(psResource["bucket"].(string)), + Key: aws.String(psResource["key"].(string)), + }, + } + } + } + } + } + + if v := dataSourceParams["service_now"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + psResource := v.(map[string]interface{}) + dataSourceParamsResource.ServiceNowParameters = &quicksight.ServiceNowParameters{ + SiteBaseUrl: aws.String(psResource["site_base_url"].(string)), + } + } + } + + if v := dataSourceParams["snowflake"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + psResource := v.(map[string]interface{}) + dataSourceParamsResource.SnowflakeParameters = &quicksight.SnowflakeParameters{ + Database: aws.String(psResource["database"].(string)), + Host: aws.String(psResource["host"].(string)), + Warehouse: aws.String(psResource["warehouse"].(string)), + } + } + } + + if v := dataSourceParams["spark"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + psResource := v.(map[string]interface{}) + dataSourceParamsResource.SparkParameters = &quicksight.SparkParameters{ + Host: aws.String(psResource["host"].(string)), + Port: aws.Int64(psResource["port"].(int64)), + } + } + } + + if v := dataSourceParams["sql_server"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + psResource := v.(map[string]interface{}) + dataSourceParamsResource.SqlServerParameters = &quicksight.SqlServerParameters{ + Database: aws.String(psResource["database"].(string)), + Host: aws.String(psResource["host"].(string)), + Port: aws.Int64(psResource["port"].(int64)), + } + } + } + + if v := dataSourceParams["teradata"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + psResource := v.(map[string]interface{}) + dataSourceParamsResource.TeradataParameters = &quicksight.TeradataParameters{ + Database: aws.String(psResource["database"].(string)), + Host: aws.String(psResource["host"].(string)), + Port: aws.Int64(psResource["port"].(int64)), + } + } + } + + if v := dataSourceParams["twitter"]; v != nil && v.([]interface{}) != nil { + for _, v := range v.([]interface{}) { + psResource := v.(map[string]interface{}) + dataSourceParamsResource.TwitterParameters = &quicksight.TwitterParameters{ + MaxRows: aws.Int64(psResource["max_rows"].(int64)), + Query: aws.String(psResource["query"].(string)), + } + } + } + + } + return aws.String(dataSourceType), dataSourceParamsResource + } + + return aws.String(""), nil +} + +func resourceAwsQuickSightDataSourceSslProperties(d *schema.ResourceData) *quicksight.SslProperties { + if v := d.Get("ssl_properties"); v != nil { + for _, v := range v.([]interface{}) { + sslProperties := v.(map[string]interface{}) + + if v, present := sslProperties["disable_ssl"]; present { + return &quicksight.SslProperties{ + DisableSsl: aws.Bool(v.(bool)), + } + } + } + } + + return nil +} + +func resourceAwsQuickSightDataSourceVpcConnectionProperties(d *schema.ResourceData) *quicksight.VpcConnectionProperties { + if v := d.Get("vpc_connection_properties"); v != nil { + for _, v := range v.([]interface{}) { + vpcConnectionProperties := v.(map[string]interface{}) + + if v := vpcConnectionProperties["vpc_connection_arn"]; v != nil && v.(string) != "" { + return &quicksight.VpcConnectionProperties{ + VpcConnectionArn: aws.String(v.(string)), + } + } + } + } + + return nil +} + +func resourceAwsQuickSightDataSourceParseID(id string) (string, string, error) { + parts := strings.SplitN(id, "/", 2) + if len(parts) < 2 || parts[0] == "" || parts[1] == "" { + return "", "", fmt.Errorf("unexpected format of ID (%s), expected AWS_ACCOUNT_ID/DATA_SOURCE_ID", id) + } + return parts[0], parts[1], nil +} + +func quicksightDataSourceArn(awsRegion string, awsAccountId string, dataSourceId string) string { + return fmt.Sprintf("arn:aws:quicksight:%s:%s:datasource/%s", awsRegion, awsAccountId, dataSourceId) +} diff --git a/aws/structure.go b/aws/structure.go index ce58a0530259..8db01a1caf32 100644 --- a/aws/structure.go +++ b/aws/structure.go @@ -34,6 +34,7 @@ import ( "github.com/aws/aws-sdk-go/service/lambda" "github.com/aws/aws-sdk-go/service/macie" "github.com/aws/aws-sdk-go/service/neptune" + "github.com/aws/aws-sdk-go/service/quicksight" "github.com/aws/aws-sdk-go/service/rds" "github.com/aws/aws-sdk-go/service/redshift" "github.com/aws/aws-sdk-go/service/route53" @@ -2496,6 +2497,120 @@ func flattenCognitoIdentityPoolRolesAttachmentMappingRules(d []*cognitoidentity. return rules } +func diffQuickSightPermissionsToGrantAndRevoke(oldPerms []interface{}, newPerms []interface{}) ([]*quicksight.ResourcePermission, []*quicksight.ResourcePermission) { + grants := diffQuickSightPermissionsLookup(oldPerms, newPerms) + + toGrant := make([]*quicksight.ResourcePermission, 0) + toRevoke := make([]*quicksight.ResourcePermission, 0) + + for principal, actions := range grants { + grant := &quicksight.ResourcePermission{ + Principal: aws.String(principal), + Actions: make([]*string, 0), + } + revoke := &quicksight.ResourcePermission{ + Principal: aws.String(principal), + Actions: make([]*string, 0), + } + + for action, shouldGrant := range actions { + if shouldGrant { + grant.Actions = append(grant.Actions, aws.String(action)) + } else { + revoke.Actions = append(revoke.Actions, aws.String(action)) + } + } + + if len(grant.Actions) > 0 { + toGrant = append(toGrant, grant) + } + + if len(revoke.Actions) > 0 { + toRevoke = append(toRevoke, revoke) + } + } + + return toGrant, toRevoke +} + +func diffQuickSightPermissionsLookup(oldPerms []interface{}, newPerms []interface{}) map[string]map[string]bool { + // Map principal to permissions. `true` means grant, `false` means + // revoke and absence means leave alone (i.e. unchanged) + grants := make(map[string]map[string]bool) + + // All new params should be granted until further notice... + for _, v := range newPerms { + s := v.(map[string]interface{}) + + if p, ok := s["principal"].(string); ok { + if _, present := grants[p]; !present { + grants[p] = make(map[string]bool) + } + + for _, v := range s["actions"].(*schema.Set).List() { + grants[p][v.(string)] = true + } + } + } + + // Don't touch principal-action combos that are unchanged, revoke combos that + // are in old but not new + for _, v := range oldPerms { + s := v.(map[string]interface{}) + + if p, ok := s["principal"].(string); ok { + if principalGrants, present := grants[p]; present { + for _, v := range s["actions"].(*schema.Set).List() { + action := v.(string) + + if _, present := principalGrants[action]; !present { + // In old but not in new so revoke + grants[p][action] = false + } else { + // In old and in new so leave alone + delete(grants[p], action) + } + } + } else { + grants[p] = make(map[string]bool) + + // The principal is not desired in new + // permissions so revoke all + for _, v := range s["actions"].(*schema.Set).List() { + grants[p][v.(string)] = false + } + } + + } + } + + return grants +} + +func flattenQuickSightPermissions(perms []*quicksight.ResourcePermission) []map[string]interface{} { + values := make([]map[string]interface{}, 0) + + for _, v := range perms { + perm := make(map[string]interface{}) + + if v == nil { + return nil + } + + if v.Principal != nil { + perm["principal"] = *v.Principal + } + + if v.Actions != nil { + perm["actions"] = flattenStringList(v.Actions) + } + + values = append(values, perm) + } + + return values +} + func flattenRedshiftLogging(ls *redshift.LoggingStatus) []interface{} { if ls == nil { return []interface{}{} diff --git a/aws/structure_test.go b/aws/structure_test.go index 199aa17954e1..190b79f097f3 100644 --- a/aws/structure_test.go +++ b/aws/structure_test.go @@ -1403,6 +1403,229 @@ func TestCognitoUserPoolSchemaAttributeMatchesStandardAttribute(t *testing.T) { } } +func TestDiffQuickSightNoPermissionsMeansNoChanges(t *testing.T) { + empty := make([]interface{}, 0) + + toGrant, toRevoke := diffQuickSightPermissionsToGrantAndRevoke(empty, empty) + + if len(toGrant) > 0 { + t.Fatal("Expected no granted permissions, got", len(toGrant)) + } + + if len(toRevoke) > 0 { + t.Fatal("Expected no revoked permissions, got", len(toRevoke)) + } +} + +func TestDiffQuickSightNoOldMeansOnlyGrant(t *testing.T) { + empty := make([]interface{}, 0) + newPerms := []interface{}{ + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action1", + "action2", + }), + }, + } + + toGrant, toRevoke := diffQuickSightPermissionsToGrantAndRevoke(empty, newPerms) + + if len(toGrant) != 1 { + t.Fatal("Expected 1 granted permission, got", len(toGrant)) + } + + if len(toRevoke) > 0 { + t.Fatal("Expected no revoked permissions, got", len(toRevoke)) + } +} + +func TestDiffQuickSightNoNewMeansOnlyRevoke(t *testing.T) { + empty := make([]interface{}, 0) + oldPerms := []interface{}{ + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action1", + "action2", + }), + }, + } + + toGrant, toRevoke := diffQuickSightPermissionsToGrantAndRevoke(oldPerms, empty) + + if len(toGrant) > 0 { + t.Fatal("Expected no granted permissions, got", len(toGrant)) + } + + if len(toRevoke) != 1 { + t.Fatal("Expected 1 revoked permission, got", len(toRevoke)) + } +} + +func TestDiffQuickSightIntersectingPermissionsMeansNoChange(t *testing.T) { + perms := []interface{}{ + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action1", + "action2", + }), + }, + } + + toGrant, toRevoke := diffQuickSightPermissionsToGrantAndRevoke(perms, perms) + + if len(toGrant) > 0 { + t.Fatal("Expected no granted permissions, got", len(toGrant)) + } + + if len(toRevoke) > 0 { + t.Fatal("Expected no revoked permissions, got", len(toRevoke)) + } +} + +func TestDiffQuickSightAdditionalNewPermissionsBecomeGrants(t *testing.T) { + oldPerms := []interface{}{ + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "oldAction", + }), + }, + } + newPerms := []interface{}{ + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "oldAction", + "newAction", + }), + }, + } + + toGrant, toRevoke := diffQuickSightPermissionsToGrantAndRevoke(oldPerms, newPerms) + + if len(toGrant) != 1 { + t.Fatal("Expected 1 granted permission, got", len(toGrant)) + } + + if len(toGrant[0].Actions) != 1 || *toGrant[0].Actions[0] != "newAction" { + t.Fatal("Expected 1 new granted action, got", len(toGrant)) + } + + if len(toRevoke) > 0 { + t.Fatal("Expected no revoked permissions, got", len(toRevoke)) + } +} + +func TestDiffQuickSightAdditionalOldPermissionsBecomeRevokes(t *testing.T) { + oldPerms := []interface{}{ + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "oldAction", + "onlyOldAction", + }), + }, + } + newPerms := []interface{}{ + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "oldAction", + }), + }, + } + + toGrant, toRevoke := diffQuickSightPermissionsToGrantAndRevoke(oldPerms, newPerms) + + if len(toRevoke) != 1 { + t.Fatal("Expected 1 revoked permission, got", len(toRevoke)) + } + + if len(toRevoke[0].Actions) != 1 || *toRevoke[0].Actions[0] != "onlyOldAction" { + t.Fatal("Expected 1 revoked action, got", len(toRevoke)) + } + + if len(toGrant) > 0 { + t.Fatal("Expected no granted permissions, got", len(toGrant)) + } +} + +func TestDiffQuickSightFatTest(t *testing.T) { + oldPerms := []interface{}{ + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action1", + }), + }, + map[string]interface{}{ + "principal": "principal2", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action1", + "action3", + "action4", + }), + }, + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action2", + }), + }, + map[string]interface{}{ + "principal": "principal3", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action5", + }), + }, + } + newPerms := []interface{}{ + // Leave action3 untouched, grant action5 and revoke action1 + // and action4 + map[string]interface{}{ + "principal": "principal2", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action3", + "action5", + }), + }, + // Should span principals, leaving all actions untouched for + // principal1 + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action1", + "action2", + }), + }, + // Don't mention principal3 which means revoke + } + + expected := map[string]map[string]bool{ + "principal2": { + "action1": false, + "action4": false, + "action5": true, + }, + "principal3": { + "action5": false, + }, + "principal1": make(map[string]bool), + } + + lookup := diffQuickSightPermissionsLookup(oldPerms, newPerms) + + if !reflect.DeepEqual(lookup, expected) { + t.Fatalf( + "Got:\n\n%#v\n\nExpected:\n\n%#v\n", + lookup, + expected) + } +} + func TestCanonicalXML(t *testing.T) { cases := []struct { Name string diff --git a/aws/tagsQuickSight.go b/aws/tagsQuickSight.go new file mode 100644 index 000000000000..cd9c87f0ee54 --- /dev/null +++ b/aws/tagsQuickSight.go @@ -0,0 +1,46 @@ +package aws + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/quicksight" +) + +func diffTagsQuickSight(oldTags, newTags []*quicksight.Tag) ([]*quicksight.Tag, []*quicksight.Tag) { + create := make(map[string]interface{}) + for _, t := range newTags { + create[aws.StringValue(t.Key)] = aws.StringValue(t.Value) + } + + var remove []*quicksight.Tag + for _, t := range oldTags { + old, ok := create[aws.StringValue(t.Key)] + if !ok || old != aws.StringValue(t.Value) { + remove = append(remove, t) + } else if ok { + delete(create, aws.StringValue(t.Key)) + } + } + + return tagsFromMapQuickSight(create), remove +} + +func tagsFromMapQuickSight(m map[string]interface{}) []*quicksight.Tag { + result := make([]*quicksight.Tag, 0, len(m)) + for k, v := range m { + t := &quicksight.Tag{ + Key: aws.String(k), + Value: aws.String(v.(string)), + } + result = append(result, t) + } + + return result +} + +func tagKeysQuickSight(ts []*quicksight.Tag) []*string { + result := make([]*string, 0, len(ts)) + for _, t := range ts { + result = append(result, t.Key) + } + return result +} diff --git a/website/docs/r/quicksight_data_source.html.markdown b/website/docs/r/quicksight_data_source.html.markdown new file mode 100644 index 000000000000..318164a5b4e0 --- /dev/null +++ b/website/docs/r/quicksight_data_source.html.markdown @@ -0,0 +1,224 @@ +--- +subcategory: "QuickSight" +layout: "aws" +page_title: "AWS: aws_quicksight_data_source" +description: |- + Manages a Resource QuickSight Data Source. +--- + +# Resource: aws_quicksight_data_source + +Resource for managing QuickSight Data Source + +## Example Usage + +```hcl +resource "aws_quicksight_data_source" "default" { + data_source_id = "abcdefg" + name = "My Cool Data in S3" + parameters { + s3 { + manifest_file_location { + bucket = "my.bucket" + key = "path/to/manifest.json" + } + } + } +} +``` + +## Argument Reference + +The QuickSight data source argument layout is a complex structure composed +of several sub-resources - these resources are laid out below. + +### Top-Level Arguments + + * `name` - (Required) A name for the data source. + + * `data_source_id` - (Required) An identifier for the data source. + + * `aws_account_id` - (Optional) The ID for the AWS account that the data source is in. Currently, you use the ID for the AWS account that contains your Amazon QuickSight account. + + * `parameters` - (Required) The [parameters](#parameters-arguments) used to connect to this data source (exactly one). + +#### Parameters Arguments + +To specify data source connection parameters, exactly one of the following sub-objects must be provided. + + * `amazon_elasticsearch` - [Parameters](#amazon-elasticsearch-arguments) for connecting to Amazon Elasticsearch. + + * `athena` - [Parameters](#athena-arguments) for connecting to Athena. + + * `aurora` - [Parameters](#aurora-arguments) for connecting to Athena. + + * `aurora_postgresql` - [Parameters](#aurora-postgresql-arguments) for connecting to Aurora Postgresql. + + * `aws_iot_analytics` - [Parameters](#aws-iot-analytics-arguments) for connecting to AWS IOT Analytics. + + * `jira` - [Parameters](#jira-arguments) for connecting to Jira. + + * `maria_db` - [Parameters](#mariadb-arguments) for connecting to MariaDB. + + * `mysql` - [Parameters](#mysql-arguments) for connecting to MySQL. + + * `postgresql` - [Parameters](#postgresql-arguments) for connecting to Postgresql. + + * `presto` - [Parameters](#presto-arguments) for connecting to Presto. + + * `redshift` - [Parameters](#redshift-arguments) for connecting to Redshift. + + * `s3` - [Parameters](#s3-arguments) for connecting to S3. + + * `service_now` - [Parameters](#servicenow-arguments) for connecting to ServiceNow. + + * `snowflake` - [Parameters](#snowflake-arguments) for connecting to Snowflake. + + * `spark` - [Parameters](#spark-arguments) for connecting to SPARK. + + * `sql_server` - [Parameters](#sqlserver-arguments) for connecting to SqlServer. + + * `teradata` - [Parameters](#teradata-arguments) for connecting to Teradata. + + * `twitter` - [Parameters](#twitter-arguments) for connecting to Twitter. + +#### Amazon Elasticsearch Arguments + + * `domain` - (Required) The domain to which to connect. + +#### Athena Arguments + + * `work_group` - (Optional) The work-group to which to connect. + +#### Aurora Arguments + + * `database` - (Required) The database to which to connect. + + * `host` - (Required) The host to which to connect. + + * `port` - (Required) The port to which to connect. + +#### Aurora Postgresql Arguments + + * `database` - (Required) The database to which to connect. + + * `host` - (Required) The host to which to connect. + + * `port` - (Required) The port to which to connect. + +#### AWS IOT Analytics Postgresql Arguments + + * `data_set_name` - (Required) The name of the data set to which to connect. + +#### Jira Arguments + + * `site_base_url` - (Required) The base URL of the Jira instance's site to which to connect. + +#### MariaDB Arguments + + * `database` - (Required) The database to which to connect. + + * `host` - (Required) The host to which to connect. + + * `port` - (Required) The port to which to connect. + +#### MySQL Arguments + + * `database` - (Required) The database to which to connect. + + * `host` - (Required) The host to which to connect. + + * `port` - (Required) The port to which to connect. + +#### Postgresql Arguments + + * `database` - (Required) The database to which to connect. + + * `host` - (Required) The host to which to connect. + + * `port` - (Required) The port to which to connect. + +#### Presto Arguments + + * `catalog` - (Required) The catalog to which to connect. + + * `host` - (Required) The host to which to connect. + + * `port` - (Required) The port to which to connect. + +#### Redshift Arguments + + * `cluster_id` - (Optional) The ID of the cluster to which to connect. + + * `database` - (Required) The database to which to connect. + + * `host` - (Optional) The host to which to connect. + + * `port` - (Optional) The port to which to connect. + +#### S3 Arguments + + * `manifest_file_location` - (Required) An [object containing the S3 location](#manifest-file-location-arguments) of the S3 manifest file. + +##### Manifest File Location Arguments + + * `bucket` - (Required) The name of the bucket that contains the manifest file. + + * `key` - (Required) The key of the manifest file within the bucket. + + +#### ServiceNow Arguments + + * `site_base_url` - (Required) The base URL of the Jira instance's site to which to connect. + +#### Snowflake Arguments + + * `database` - (Required) The database to which to connect. + + * `host` - (Required) The host to which to connect. + + * `warehouse` - (Required) The warehouse to which to connect. + +#### SPARK Arguments + + * `host` - (Required) The host to which to connect. + + * `port` - (Required) The warehouse to which to connect. + +#### SqlServer Arguments + + * `database` - (Required) The database to which to connect. + + * `host` - (Required) The host to which to connect. + + * `port` - (Required) The warehouse to which to connect. + +#### Teradata Arguments + + * `database` - (Required) The database to which to connect. + + * `host` - (Required) The host to which to connect. + + * `port` - (Required) The warehouse to which to connect. + +#### Twitter Arguments + + * `max_rows` - (Required) The maximum number of rows to query. + + * `query` - (Required) The Twitter query to retrieve the data. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - Amazon Resource Name (ARN) of the data source + +* `type` - A key indicating which data source type was inferred from the passed `parameters` + +## Import + +A QuickSight data source can be imported using the AWS account ID, and data source ID name separated by `/`. + +``` +$ terraform import aws_quicksight_data_source.example 123456789123/my-data-source-id +``` From 30b18331603997d899d86863c223d80dbaa1616f Mon Sep 17 00:00:00 2001 From: Luke Cernetic Date: Fri, 27 Aug 2021 10:02:24 -0700 Subject: [PATCH 02/12] added sensitive tag to creds and added creds to docs --- aws/resource_aws_quicksight_data_source.go | 6 ++++++ website/docs/r/quicksight_data_source.html.markdown | 2 ++ 2 files changed, 8 insertions(+) diff --git a/aws/resource_aws_quicksight_data_source.go b/aws/resource_aws_quicksight_data_source.go index 741834891da8..3a6150025314 100644 --- a/aws/resource_aws_quicksight_data_source.go +++ b/aws/resource_aws_quicksight_data_source.go @@ -53,11 +53,17 @@ func resourceAwsQuickSightDataSource() *schema.Resource { Type: schema.TypeString, Required: true, ValidateFunc: validation.NoZeroValues, + + // maybe don't need? + Sensitive: true, }, "username": { Type: schema.TypeString, Required: true, ValidateFunc: validation.NoZeroValues, + + // maybe don't need? + Sensitive: true, }, }, }, diff --git a/website/docs/r/quicksight_data_source.html.markdown b/website/docs/r/quicksight_data_source.html.markdown index 318164a5b4e0..88c0209748e2 100644 --- a/website/docs/r/quicksight_data_source.html.markdown +++ b/website/docs/r/quicksight_data_source.html.markdown @@ -42,6 +42,8 @@ of several sub-resources - these resources are laid out below. * `parameters` - (Required) The [parameters](#parameters-arguments) used to connect to this data source (exactly one). + * `credentials` - (Optional) TODO + #### Parameters Arguments To specify data source connection parameters, exactly one of the following sub-objects must be provided. From 31705e57b7a8b5941ea2bb41745cf36651f36d4b Mon Sep 17 00:00:00 2001 From: Luke Cernetic Date: Fri, 27 Aug 2021 12:21:56 -0700 Subject: [PATCH 03/12] added test file --- ...esource_aws_quicksight_data_source_test.go | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 aws/resource_aws_quicksight_data_source_test.go diff --git a/aws/resource_aws_quicksight_data_source_test.go b/aws/resource_aws_quicksight_data_source_test.go new file mode 100644 index 000000000000..779feda6feaa --- /dev/null +++ b/aws/resource_aws_quicksight_data_source_test.go @@ -0,0 +1,193 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/service/quicksight" +) + +func TestAccAWSQuickSightDataSource_basic(t *testing.T) { + var dataSource quicksight.DataSource + resourceName := "aws_quicksight_data_source.default" + rName1 := acctest.RandomWithPrefix("tf-acc-test") + rId1 := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckQuickSightDataSourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSQuickSightDataSourceConfig(rId1, rName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckQuickSightDataSourceExists(resourceName, &dataSource), + resource.TestCheckResourceAttr(resourceName, "data_source_id", rId1), + testAccCheckResourceAttrRegionalARN(resourceName, "arn", "quicksight", fmt.Sprintf("datasource/%s", rId1)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSQuickSightDataSource_disappears(t *testing.T) { + var dataSource quicksight.DataSource + resourceName := "aws_quicksight_data_source.default" + rName := acctest.RandomWithPrefix("tf-acc-test") + rId := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckQuickSightDataSourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSQuickSightDataSourceConfig(rId, rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckQuickSightDataSourceExists(resourceName, &dataSource), + testAccCheckQuickSightDataSourceDisappears(&dataSource), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckQuickSightDataSourceExists(resourceName string, dataSource *quicksight.DataSource) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + awsAccountID, dataSourceId, err := resourceAwsQuickSightDataSourceParseID(rs.Primary.ID) + if err != nil { + return err + } + + conn := testAccProvider.Meta().(*AWSClient).quicksightconn + + input := &quicksight.DescribeDataSourceInput{ + AwsAccountId: aws.String(awsAccountID), + DataSourceId: aws.String(dataSourceId), + } + + output, err := conn.DescribeDataSource(input) + + if err != nil { + return err + } + + if output == nil || output.DataSource == nil { + return fmt.Errorf("QuickSight Data Source (%s) not found", rs.Primary.ID) + } + + *dataSource = *output.DataSource + + return nil + } +} + +func testAccCheckQuickSightDataSourceDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).quicksightconn + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_quicksight_data_source" { + continue + } + + awsAccountID, dataSourceId, err := resourceAwsQuickSightDataSourceParseID(rs.Primary.ID) + if err != nil { + return err + } + + _, err = conn.DescribeDataSource(&quicksight.DescribeDataSourceInput{ + AwsAccountId: aws.String(awsAccountID), + DataSourceId: aws.String(dataSourceId), + }) + if isAWSErr(err, quicksight.ErrCodeResourceNotFoundException, "") { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("QuickSight Data Source '%s' was not deleted properly", rs.Primary.ID) + } + + return nil +} + +func testAccCheckQuickSightDataSourceDisappears(v *quicksight.DataSource) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).quicksightconn + + arn, err := arn.Parse(aws.StringValue(v.Arn)) + if err != nil { + return err + } + + input := &quicksight.DeleteDataSourceInput{ + AwsAccountId: aws.String(arn.AccountID), + DataSourceId: v.DataSourceId, + } + + if _, err := conn.DeleteDataSource(input); err != nil { + return err + } + + return nil + } +} + +func testAccAWSQuickSightDataSourceConfig(rId string, rName string) string { + manifestKey := acctest.RandomWithPrefix("tf-acc-test") + + return fmt.Sprintf(` +resource "aws_quicksight_data_source" "default" { + data_source_id = %[1]q + name = %[2]q + parameters { + s3 { + manifest_file_location { + bucket = aws_s3_bucket.bucket.bucket + key = aws_s3_bucket_object.manifest.key + } + } + } +} +resource "aws_s3_bucket" "bucket" { + acl = "public-read" +} +resource "aws_s3_bucket_object" "manifest" { + bucket = aws_s3_bucket.bucket.bucket + key = %[3]q + content = < Date: Wed, 15 Sep 2021 08:06:51 -0700 Subject: [PATCH 04/12] changed crud to crudWithoutTimeout --- aws/resource_aws_quicksight_data_source.go | 46 +++++++++++----------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/aws/resource_aws_quicksight_data_source.go b/aws/resource_aws_quicksight_data_source.go index 3a6150025314..c922866d9617 100644 --- a/aws/resource_aws_quicksight_data_source.go +++ b/aws/resource_aws_quicksight_data_source.go @@ -1,6 +1,7 @@ package aws import ( + "context" "fmt" "log" "strings" @@ -8,6 +9,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/quicksight" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -15,10 +17,10 @@ import ( func resourceAwsQuickSightDataSource() *schema.Resource { return &schema.Resource{ - Create: resourceAwsQuickSightDataSourceCreate, - Read: resourceAwsQuickSightDataSourceRead, - Update: resourceAwsQuickSightDataSourceUpdate, - Delete: resourceAwsQuickSightDataSourceDelete, + CreateWithoutTimeout: resourceAwsQuickSightDataSourceCreate, + ReadWithoutTimeout: resourceAwsQuickSightDataSourceRead, + UpdateWithoutTimeout: resourceAwsQuickSightDataSourceUpdate, + DeleteWithoutTimeout: resourceAwsQuickSightDataSourceDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, @@ -533,7 +535,7 @@ func resourceAwsQuickSightDataSource() *schema.Resource { } } -func resourceAwsQuickSightDataSourceCreate(d *schema.ResourceData, meta interface{}) error { +func resourceAwsQuickSightDataSourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).quicksightconn awsAccountId := meta.(*AWSClient).accountid @@ -586,20 +588,20 @@ func resourceAwsQuickSightDataSourceCreate(d *schema.ResourceData, meta interfac _, err := conn.CreateDataSource(params) if err != nil { - return fmt.Errorf("Error creating QuickSight Data Source: %s", err) + return diag.Errorf("Error creating QuickSight Data Source: %s", err) } d.SetId(fmt.Sprintf("%s/%s", awsAccountId, id)) - return resourceAwsQuickSightDataSourceRead(d, meta) + return resourceAwsQuickSightDataSourceRead(ctx, d, meta) } -func resourceAwsQuickSightDataSourceRead(d *schema.ResourceData, meta interface{}) error { +func resourceAwsQuickSightDataSourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).quicksightconn awsAccountId, dataSourceId, err := resourceAwsQuickSightDataSourceParseID(d.Id()) if err != nil { - return err + return diag.FromErr(err) } descOpts := &quicksight.DescribeDataSourceInput{ @@ -636,7 +638,7 @@ func resourceAwsQuickSightDataSourceRead(d *schema.ResourceData, meta interface{ return nil } if err != nil { - return fmt.Errorf("Error describing QuickSight Data Source (%s): %s", d.Id(), err) + return diag.Errorf("Error describing QuickSight Data Source (%s): %s", d.Id(), err) } permsResp, err := conn.DescribeDataSourcePermissions(&quicksight.DescribeDataSourcePermissionsInput{ @@ -645,7 +647,7 @@ func resourceAwsQuickSightDataSourceRead(d *schema.ResourceData, meta interface{ }) if err != nil { - return fmt.Errorf("Error describing QuickSight Data Source permissions (%s): %s", d.Id(), err) + return diag.Errorf("Error describing QuickSight Data Source permissions (%s): %s", d.Id(), err) } dataSource := dataSourceResp.DataSource @@ -656,7 +658,7 @@ func resourceAwsQuickSightDataSourceRead(d *schema.ResourceData, meta interface{ d.Set("aws_account_id", awsAccountId) if err := d.Set("permission", flattenQuickSightPermissions(permsResp.Permissions)); err != nil { - return fmt.Errorf("Error setting permission error: %#v", err) + return diag.Errorf("Error setting permission error: %#v", err) } params := map[string]interface{}{} @@ -876,12 +878,12 @@ func resourceAwsQuickSightDataSourceRead(d *schema.ResourceData, meta interface{ return nil } -func resourceAwsQuickSightDataSourceUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceAwsQuickSightDataSourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).quicksightconn awsAccountId, dataSourceId, err := resourceAwsQuickSightDataSourceParseID(d.Id()) if err != nil { - return err + return diag.FromErr(err) } params := &quicksight.UpdateDataSourceInput{ @@ -914,7 +916,7 @@ func resourceAwsQuickSightDataSourceUpdate(d *schema.ResourceData, meta interfac _, err := conn.UpdateDataSourcePermissions(params) if err != nil { - return fmt.Errorf("Error updating QuickSight Data Source permissions: %s", err) + return diag.Errorf("Error updating QuickSight Data Source permissions: %s", err) } } } @@ -935,7 +937,7 @@ func resourceAwsQuickSightDataSourceUpdate(d *schema.ResourceData, meta interfac TagKeys: tagKeysQuickSight(r), }) if err != nil { - return fmt.Errorf("Error deleting QuickSight Data Source tags: %s", err) + return diag.Errorf("Error deleting QuickSight Data Source tags: %s", err) } } @@ -945,7 +947,7 @@ func resourceAwsQuickSightDataSourceUpdate(d *schema.ResourceData, meta interfac Tags: c, }) if err != nil { - return fmt.Errorf("Error updating QuickSight Data Source tags: %s", err) + return diag.Errorf("Error updating QuickSight Data Source tags: %s", err) } } } @@ -961,18 +963,18 @@ func resourceAwsQuickSightDataSourceUpdate(d *schema.ResourceData, meta interfac return nil } if err != nil { - return fmt.Errorf("Error updating QuickSight Data Source %s: %s", d.Id(), err) + return diag.Errorf("Error updating QuickSight Data Source %s: %s", d.Id(), err) } - return resourceAwsQuickSightDataSourceRead(d, meta) + return resourceAwsQuickSightDataSourceRead(ctx, d, meta) } -func resourceAwsQuickSightDataSourceDelete(d *schema.ResourceData, meta interface{}) error { +func resourceAwsQuickSightDataSourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).quicksightconn awsAccountId, dataSourceId, err := resourceAwsQuickSightDataSourceParseID(d.Id()) if err != nil { - return err + return diag.FromErr(err) } deleteOpts := &quicksight.DeleteDataSourceInput{ @@ -984,7 +986,7 @@ func resourceAwsQuickSightDataSourceDelete(d *schema.ResourceData, meta interfac if isAWSErr(err, quicksight.ErrCodeResourceNotFoundException, "") { return nil } - return fmt.Errorf("Error deleting QuickSight Data Source %s: %s", d.Id(), err) + return diag.Errorf("Error deleting QuickSight Data Source %s: %s", d.Id(), err) } return nil From b08f939a61b7aae9d9f9cdbb159867590ecda662 Mon Sep 17 00:00:00 2001 From: Luke Cernetic Date: Wed, 15 Sep 2021 13:49:26 -0700 Subject: [PATCH 05/12] changed test variable names and edited error messages --- aws/resource_aws_quicksight_data_source.go | 18 +++++++++--------- ...resource_aws_quicksight_data_source_test.go | 10 +++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/aws/resource_aws_quicksight_data_source.go b/aws/resource_aws_quicksight_data_source.go index c922866d9617..21091c00d32c 100644 --- a/aws/resource_aws_quicksight_data_source.go +++ b/aws/resource_aws_quicksight_data_source.go @@ -588,7 +588,7 @@ func resourceAwsQuickSightDataSourceCreate(ctx context.Context, d *schema.Resour _, err := conn.CreateDataSource(params) if err != nil { - return diag.Errorf("Error creating QuickSight Data Source: %s", err) + return diag.Errorf("error creating QuickSight Data Source: %s", err) } d.SetId(fmt.Sprintf("%s/%s", awsAccountId, id)) @@ -638,7 +638,7 @@ func resourceAwsQuickSightDataSourceRead(ctx context.Context, d *schema.Resource return nil } if err != nil { - return diag.Errorf("Error describing QuickSight Data Source (%s): %s", d.Id(), err) + return diag.Errorf("error describing QuickSight Data Source (%s): %s", d.Id(), err) } permsResp, err := conn.DescribeDataSourcePermissions(&quicksight.DescribeDataSourcePermissionsInput{ @@ -647,7 +647,7 @@ func resourceAwsQuickSightDataSourceRead(ctx context.Context, d *schema.Resource }) if err != nil { - return diag.Errorf("Error describing QuickSight Data Source permissions (%s): %s", d.Id(), err) + return diag.Errorf("error describing QuickSight Data Source permissions (%s): %s", d.Id(), err) } dataSource := dataSourceResp.DataSource @@ -658,7 +658,7 @@ func resourceAwsQuickSightDataSourceRead(ctx context.Context, d *schema.Resource d.Set("aws_account_id", awsAccountId) if err := d.Set("permission", flattenQuickSightPermissions(permsResp.Permissions)); err != nil { - return diag.Errorf("Error setting permission error: %#v", err) + return diag.Errorf("error setting permission error: %#v", err) } params := map[string]interface{}{} @@ -916,7 +916,7 @@ func resourceAwsQuickSightDataSourceUpdate(ctx context.Context, d *schema.Resour _, err := conn.UpdateDataSourcePermissions(params) if err != nil { - return diag.Errorf("Error updating QuickSight Data Source permissions: %s", err) + return diag.Errorf("error updating QuickSight Data Source (%s) permissions: %s", aws.String(dataSourceId), err) } } } @@ -937,7 +937,7 @@ func resourceAwsQuickSightDataSourceUpdate(ctx context.Context, d *schema.Resour TagKeys: tagKeysQuickSight(r), }) if err != nil { - return diag.Errorf("Error deleting QuickSight Data Source tags: %s", err) + return diag.Errorf("error deleting QuickSight Data Source (%s) tags: %s", d.Id(), err) } } @@ -947,7 +947,7 @@ func resourceAwsQuickSightDataSourceUpdate(ctx context.Context, d *schema.Resour Tags: c, }) if err != nil { - return diag.Errorf("Error updating QuickSight Data Source tags: %s", err) + return diag.Errorf("error updating QuickSight Data Source (%s) tags: %s", d.Id(), err) } } } @@ -963,7 +963,7 @@ func resourceAwsQuickSightDataSourceUpdate(ctx context.Context, d *schema.Resour return nil } if err != nil { - return diag.Errorf("Error updating QuickSight Data Source %s: %s", d.Id(), err) + return diag.Errorf("error updating QuickSight Data Source (%s): %s", d.Id(), err) } return resourceAwsQuickSightDataSourceRead(ctx, d, meta) @@ -986,7 +986,7 @@ func resourceAwsQuickSightDataSourceDelete(ctx context.Context, d *schema.Resour if isAWSErr(err, quicksight.ErrCodeResourceNotFoundException, "") { return nil } - return diag.Errorf("Error deleting QuickSight Data Source %s: %s", d.Id(), err) + return diag.Errorf("error deleting QuickSight Data Source (%s): %s", d.Id(), err) } return nil diff --git a/aws/resource_aws_quicksight_data_source_test.go b/aws/resource_aws_quicksight_data_source_test.go index 779feda6feaa..8ff6c1898404 100644 --- a/aws/resource_aws_quicksight_data_source_test.go +++ b/aws/resource_aws_quicksight_data_source_test.go @@ -16,8 +16,8 @@ import ( func TestAccAWSQuickSightDataSource_basic(t *testing.T) { var dataSource quicksight.DataSource resourceName := "aws_quicksight_data_source.default" - rName1 := acctest.RandomWithPrefix("tf-acc-test") - rId1 := acctest.RandomWithPrefix("tf-acc-test") + rName := acctest.RandomWithPrefix("tf-acc-test") + rId := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -25,11 +25,11 @@ func TestAccAWSQuickSightDataSource_basic(t *testing.T) { CheckDestroy: testAccCheckQuickSightDataSourceDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSQuickSightDataSourceConfig(rId1, rName1), + Config: testAccAWSQuickSightDataSourceConfig(rId, rName), Check: resource.ComposeTestCheckFunc( testAccCheckQuickSightDataSourceExists(resourceName, &dataSource), - resource.TestCheckResourceAttr(resourceName, "data_source_id", rId1), - testAccCheckResourceAttrRegionalARN(resourceName, "arn", "quicksight", fmt.Sprintf("datasource/%s", rId1)), + resource.TestCheckResourceAttr(resourceName, "data_source_id", rId), + testAccCheckResourceAttrRegionalARN(resourceName, "arn", "quicksight", fmt.Sprintf("datasource/%s", rId)), ), }, { From 880f8f492ecaa528174f586cee69f252882bbfe8 Mon Sep 17 00:00:00 2001 From: Luke Cernetic Date: Fri, 17 Sep 2021 08:29:36 -0700 Subject: [PATCH 06/12] removed credential sensitive comments and added credentials to markdown --- aws/resource_aws_quicksight_data_source.go | 8 ++------ website/docs/r/quicksight_data_source.html.markdown | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/aws/resource_aws_quicksight_data_source.go b/aws/resource_aws_quicksight_data_source.go index 21091c00d32c..adc7d9274d6a 100644 --- a/aws/resource_aws_quicksight_data_source.go +++ b/aws/resource_aws_quicksight_data_source.go @@ -55,17 +55,13 @@ func resourceAwsQuickSightDataSource() *schema.Resource { Type: schema.TypeString, Required: true, ValidateFunc: validation.NoZeroValues, - - // maybe don't need? - Sensitive: true, + Sensitive: true, }, "username": { Type: schema.TypeString, Required: true, ValidateFunc: validation.NoZeroValues, - - // maybe don't need? - Sensitive: true, + Sensitive: true, }, }, }, diff --git a/website/docs/r/quicksight_data_source.html.markdown b/website/docs/r/quicksight_data_source.html.markdown index 88c0209748e2..95e050aea1f9 100644 --- a/website/docs/r/quicksight_data_source.html.markdown +++ b/website/docs/r/quicksight_data_source.html.markdown @@ -42,7 +42,7 @@ of several sub-resources - these resources are laid out below. * `parameters` - (Required) The [parameters](#parameters-arguments) used to connect to this data source (exactly one). - * `credentials` - (Optional) TODO + * `credentials` - (Optional) The credentials Amazon QuickSight that uses to connect to your underlying source. Currently, only credentials based on user name and password are supported. #### Parameters Arguments From a33f839835f88f3ee507594400156e738fd92b04 Mon Sep 17 00:00:00 2001 From: Luke Cernetic Date: Mon, 20 Sep 2021 14:28:34 -0700 Subject: [PATCH 07/12] fixed lint errors --- aws/resource_aws_quicksight_data_source.go | 2 +- aws/resource_aws_quicksight_data_source_test.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/aws/resource_aws_quicksight_data_source.go b/aws/resource_aws_quicksight_data_source.go index adc7d9274d6a..93b19a0275dd 100644 --- a/aws/resource_aws_quicksight_data_source.go +++ b/aws/resource_aws_quicksight_data_source.go @@ -1320,5 +1320,5 @@ func resourceAwsQuickSightDataSourceParseID(id string) (string, string, error) { } func quicksightDataSourceArn(awsRegion string, awsAccountId string, dataSourceId string) string { - return fmt.Sprintf("arn:aws:quicksight:%s:%s:datasource/%s", awsRegion, awsAccountId, dataSourceId) + return fmt.Sprintf("arn:${data.aws_partition.current.partition}:quicksight:%s:%s:datasource/%s", awsRegion, awsAccountId, dataSourceId) } diff --git a/aws/resource_aws_quicksight_data_source_test.go b/aws/resource_aws_quicksight_data_source_test.go index 8ff6c1898404..2cc9cafd7f95 100644 --- a/aws/resource_aws_quicksight_data_source_test.go +++ b/aws/resource_aws_quicksight_data_source_test.go @@ -22,6 +22,7 @@ func TestAccAWSQuickSightDataSource_basic(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, + ErrorCheck: testAccErrorCheck(t, quicksight.EndpointsID), CheckDestroy: testAccCheckQuickSightDataSourceDestroy, Steps: []resource.TestStep{ { @@ -50,6 +51,7 @@ func TestAccAWSQuickSightDataSource_disappears(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, + ErrorCheck: testAccErrorCheck(t, quicksight.EndpointsID), CheckDestroy: testAccCheckQuickSightDataSourceDestroy, Steps: []resource.TestStep{ { From 5532674e84392750ddfcd4cd900b14837ea25d38 Mon Sep 17 00:00:00 2001 From: Luke Cernetic Date: Mon, 20 Sep 2021 14:57:13 -0700 Subject: [PATCH 08/12] removed change to arn creation --- aws/resource_aws_quicksight_data_source.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_quicksight_data_source.go b/aws/resource_aws_quicksight_data_source.go index 93b19a0275dd..adc7d9274d6a 100644 --- a/aws/resource_aws_quicksight_data_source.go +++ b/aws/resource_aws_quicksight_data_source.go @@ -1320,5 +1320,5 @@ func resourceAwsQuickSightDataSourceParseID(id string) (string, string, error) { } func quicksightDataSourceArn(awsRegion string, awsAccountId string, dataSourceId string) string { - return fmt.Sprintf("arn:${data.aws_partition.current.partition}:quicksight:%s:%s:datasource/%s", awsRegion, awsAccountId, dataSourceId) + return fmt.Sprintf("arn:aws:quicksight:%s:%s:datasource/%s", awsRegion, awsAccountId, dataSourceId) } From baf4e92c1f48ea3596da79ec69d539e3fca038a1 Mon Sep 17 00:00:00 2001 From: Luke Cernetic Date: Mon, 20 Sep 2021 15:34:10 -0700 Subject: [PATCH 09/12] added datasource resorce to provider.go --- aws/provider.go | 1 + 1 file changed, 1 insertion(+) diff --git a/aws/provider.go b/aws/provider.go index 24376297ed17..2b3cd8fe26b0 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -972,6 +972,7 @@ func Provider() *schema.Provider { "aws_prometheus_workspace": resourceAwsPrometheusWorkspace(), "aws_proxy_protocol_policy": resourceAwsProxyProtocolPolicy(), "aws_qldb_ledger": resourceAwsQLDBLedger(), + "aws_quicksight_data_source": resourceAwsQuickSightDataSource(), "aws_quicksight_group": resourceAwsQuickSightGroup(), "aws_quicksight_group_membership": resourceAwsQuickSightGroupMembership(), "aws_quicksight_user": resourceAwsQuickSightUser(), From 9bad273b74b7dbcc8c37b6cd188ee391eff9d0a8 Mon Sep 17 00:00:00 2001 From: Luke Cernetic Date: Mon, 20 Sep 2021 15:43:52 -0700 Subject: [PATCH 10/12] fixed print error --- aws/resource_aws_quicksight_data_source.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_quicksight_data_source.go b/aws/resource_aws_quicksight_data_source.go index adc7d9274d6a..a92960f47db8 100644 --- a/aws/resource_aws_quicksight_data_source.go +++ b/aws/resource_aws_quicksight_data_source.go @@ -912,7 +912,7 @@ func resourceAwsQuickSightDataSourceUpdate(ctx context.Context, d *schema.Resour _, err := conn.UpdateDataSourcePermissions(params) if err != nil { - return diag.Errorf("error updating QuickSight Data Source (%s) permissions: %s", aws.String(dataSourceId), err) + return diag.Errorf("error updating QuickSight Data Source (%s) permissions: %s", dataSourceId, err) } } } From ac50ce91857fb4cc0061ac2f0ecdaf1a92e67abd Mon Sep 17 00:00:00 2001 From: Luke Cernetic Date: Mon, 20 Sep 2021 15:59:43 -0700 Subject: [PATCH 11/12] fixed website lint --- .../r/quicksight_data_source.html.markdown | 136 +++++++++--------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/website/docs/r/quicksight_data_source.html.markdown b/website/docs/r/quicksight_data_source.html.markdown index 95e050aea1f9..44f4807cfec7 100644 --- a/website/docs/r/quicksight_data_source.html.markdown +++ b/website/docs/r/quicksight_data_source.html.markdown @@ -12,7 +12,7 @@ Resource for managing QuickSight Data Source ## Example Usage -```hcl +```terraform resource "aws_quicksight_data_source" "default" { data_source_id = "abcdefg" name = "My Cool Data in S3" @@ -20,7 +20,7 @@ resource "aws_quicksight_data_source" "default" { s3 { manifest_file_location { bucket = "my.bucket" - key = "path/to/manifest.json" + key = "path/to/manifest.json" } } } @@ -34,180 +34,180 @@ of several sub-resources - these resources are laid out below. ### Top-Level Arguments - * `name` - (Required) A name for the data source. +* `name` - (Required) A name for the data source. - * `data_source_id` - (Required) An identifier for the data source. +* `data_source_id` - (Required) An identifier for the data source. - * `aws_account_id` - (Optional) The ID for the AWS account that the data source is in. Currently, you use the ID for the AWS account that contains your Amazon QuickSight account. +* `aws_account_id` - (Optional) The ID for the AWS account that the data source is in. Currently, you use the ID for the AWS account that contains your Amazon QuickSight account. - * `parameters` - (Required) The [parameters](#parameters-arguments) used to connect to this data source (exactly one). +* `parameters` - (Required) The [parameters](#parameters-arguments) used to connect to this data source (exactly one). - * `credentials` - (Optional) The credentials Amazon QuickSight that uses to connect to your underlying source. Currently, only credentials based on user name and password are supported. +* `credentials` - (Optional) The credentials Amazon QuickSight that uses to connect to your underlying source. Currently, only credentials based on user name and password are supported. #### Parameters Arguments To specify data source connection parameters, exactly one of the following sub-objects must be provided. - * `amazon_elasticsearch` - [Parameters](#amazon-elasticsearch-arguments) for connecting to Amazon Elasticsearch. +* `amazon_elasticsearch` - [Parameters](#amazon-elasticsearch-arguments) for connecting to Amazon Elasticsearch. - * `athena` - [Parameters](#athena-arguments) for connecting to Athena. +* `athena` - [Parameters](#athena-arguments) for connecting to Athena. - * `aurora` - [Parameters](#aurora-arguments) for connecting to Athena. +* `aurora` - [Parameters](#aurora-arguments) for connecting to Athena. - * `aurora_postgresql` - [Parameters](#aurora-postgresql-arguments) for connecting to Aurora Postgresql. +* `aurora_postgresql` - [Parameters](#aurora-postgresql-arguments) for connecting to Aurora Postgresql. - * `aws_iot_analytics` - [Parameters](#aws-iot-analytics-arguments) for connecting to AWS IOT Analytics. +* `aws_iot_analytics` - [Parameters](#aws-iot-analytics-arguments) for connecting to AWS IOT Analytics. - * `jira` - [Parameters](#jira-arguments) for connecting to Jira. +* `jira` - [Parameters](#jira-arguments) for connecting to Jira. - * `maria_db` - [Parameters](#mariadb-arguments) for connecting to MariaDB. +* `maria_db` - [Parameters](#mariadb-arguments) for connecting to MariaDB. - * `mysql` - [Parameters](#mysql-arguments) for connecting to MySQL. +* `mysql` - [Parameters](#mysql-arguments) for connecting to MySQL. - * `postgresql` - [Parameters](#postgresql-arguments) for connecting to Postgresql. +* `postgresql` - [Parameters](#postgresql-arguments) for connecting to Postgresql. - * `presto` - [Parameters](#presto-arguments) for connecting to Presto. +* `presto` - [Parameters](#presto-arguments) for connecting to Presto. - * `redshift` - [Parameters](#redshift-arguments) for connecting to Redshift. +* `redshift` - [Parameters](#redshift-arguments) for connecting to Redshift. - * `s3` - [Parameters](#s3-arguments) for connecting to S3. +* `s3` - [Parameters](#s3-arguments) for connecting to S3. - * `service_now` - [Parameters](#servicenow-arguments) for connecting to ServiceNow. +* `service_now` - [Parameters](#servicenow-arguments) for connecting to ServiceNow. - * `snowflake` - [Parameters](#snowflake-arguments) for connecting to Snowflake. +* `snowflake` - [Parameters](#snowflake-arguments) for connecting to Snowflake. - * `spark` - [Parameters](#spark-arguments) for connecting to SPARK. +* `spark` - [Parameters](#spark-arguments) for connecting to SPARK. - * `sql_server` - [Parameters](#sqlserver-arguments) for connecting to SqlServer. +* `sql_server` - [Parameters](#sqlserver-arguments) for connecting to SqlServer. - * `teradata` - [Parameters](#teradata-arguments) for connecting to Teradata. +* `teradata` - [Parameters](#teradata-arguments) for connecting to Teradata. - * `twitter` - [Parameters](#twitter-arguments) for connecting to Twitter. +* `twitter` - [Parameters](#twitter-arguments) for connecting to Twitter. #### Amazon Elasticsearch Arguments - * `domain` - (Required) The domain to which to connect. +* `domain` - (Required) The domain to which to connect. #### Athena Arguments - * `work_group` - (Optional) The work-group to which to connect. +* `work_group` - (Optional) The work-group to which to connect. #### Aurora Arguments - * `database` - (Required) The database to which to connect. +* `database` - (Required) The database to which to connect. - * `host` - (Required) The host to which to connect. +* `host` - (Required) The host to which to connect. - * `port` - (Required) The port to which to connect. +* `port` - (Required) The port to which to connect. #### Aurora Postgresql Arguments - * `database` - (Required) The database to which to connect. +* `database` - (Required) The database to which to connect. - * `host` - (Required) The host to which to connect. +* `host` - (Required) The host to which to connect. - * `port` - (Required) The port to which to connect. +* `port` - (Required) The port to which to connect. #### AWS IOT Analytics Postgresql Arguments - * `data_set_name` - (Required) The name of the data set to which to connect. +* `data_set_name` - (Required) The name of the data set to which to connect. #### Jira Arguments - * `site_base_url` - (Required) The base URL of the Jira instance's site to which to connect. +* `site_base_url` - (Required) The base URL of the Jira instance's site to which to connect. #### MariaDB Arguments - * `database` - (Required) The database to which to connect. +* `database` - (Required) The database to which to connect. - * `host` - (Required) The host to which to connect. +* `host` - (Required) The host to which to connect. - * `port` - (Required) The port to which to connect. +* `port` - (Required) The port to which to connect. #### MySQL Arguments - * `database` - (Required) The database to which to connect. +* `database` - (Required) The database to which to connect. - * `host` - (Required) The host to which to connect. +* `host` - (Required) The host to which to connect. - * `port` - (Required) The port to which to connect. +* `port` - (Required) The port to which to connect. #### Postgresql Arguments - * `database` - (Required) The database to which to connect. +* `database` - (Required) The database to which to connect. - * `host` - (Required) The host to which to connect. +* `host` - (Required) The host to which to connect. - * `port` - (Required) The port to which to connect. +* `port` - (Required) The port to which to connect. #### Presto Arguments - * `catalog` - (Required) The catalog to which to connect. +* `catalog` - (Required) The catalog to which to connect. - * `host` - (Required) The host to which to connect. +* `host` - (Required) The host to which to connect. - * `port` - (Required) The port to which to connect. +* `port` - (Required) The port to which to connect. #### Redshift Arguments - * `cluster_id` - (Optional) The ID of the cluster to which to connect. +* `cluster_id` - (Optional) The ID of the cluster to which to connect. - * `database` - (Required) The database to which to connect. +* `database` - (Required) The database to which to connect. - * `host` - (Optional) The host to which to connect. +* `host` - (Optional) The host to which to connect. - * `port` - (Optional) The port to which to connect. +* `port` - (Optional) The port to which to connect. #### S3 Arguments - * `manifest_file_location` - (Required) An [object containing the S3 location](#manifest-file-location-arguments) of the S3 manifest file. +* `manifest_file_location` - (Required) An [object containing the S3 location](#manifest-file-location-arguments) of the S3 manifest file. ##### Manifest File Location Arguments - * `bucket` - (Required) The name of the bucket that contains the manifest file. +* `bucket` - (Required) The name of the bucket that contains the manifest file. - * `key` - (Required) The key of the manifest file within the bucket. +* `key` - (Required) The key of the manifest file within the bucket. #### ServiceNow Arguments - * `site_base_url` - (Required) The base URL of the Jira instance's site to which to connect. +* `site_base_url` - (Required) The base URL of the Jira instance's site to which to connect. #### Snowflake Arguments - * `database` - (Required) The database to which to connect. +* `database` - (Required) The database to which to connect. - * `host` - (Required) The host to which to connect. +* `host` - (Required) The host to which to connect. - * `warehouse` - (Required) The warehouse to which to connect. +* `warehouse` - (Required) The warehouse to which to connect. #### SPARK Arguments - * `host` - (Required) The host to which to connect. +* `host` - (Required) The host to which to connect. - * `port` - (Required) The warehouse to which to connect. +* `port` - (Required) The warehouse to which to connect. #### SqlServer Arguments - * `database` - (Required) The database to which to connect. +* `database` - (Required) The database to which to connect. - * `host` - (Required) The host to which to connect. +* `host` - (Required) The host to which to connect. - * `port` - (Required) The warehouse to which to connect. +* `port` - (Required) The warehouse to which to connect. #### Teradata Arguments - * `database` - (Required) The database to which to connect. +* `database` - (Required) The database to which to connect. - * `host` - (Required) The host to which to connect. +* `host` - (Required) The host to which to connect. - * `port` - (Required) The warehouse to which to connect. +* `port` - (Required) The warehouse to which to connect. #### Twitter Arguments - * `max_rows` - (Required) The maximum number of rows to query. +* `max_rows` - (Required) The maximum number of rows to query. - * `query` - (Required) The Twitter query to retrieve the data. +* `query` - (Required) The Twitter query to retrieve the data. ## Attributes Reference From e2672ffa170d58632671dcd7df89783366e78f1f Mon Sep 17 00:00:00 2001 From: Angie Pinilla Date: Wed, 29 Sep 2021 20:09:00 -0400 Subject: [PATCH 12/12] CR updates; refactor and add test coverage --- .changelog/20710.txt | 3 + .../service/quicksight/waiter/status.go | 31 + .../service/quicksight/waiter/waiter.go | 61 + aws/provider.go | 2 +- aws/resource_aws_quicksight_data_source.go | 1564 ++++++++++------- ...esource_aws_quicksight_data_source_test.go | 636 ++++++- aws/structure.go | 115 -- aws/structure_test.go | 223 --- aws/tagsQuickSight.go | 46 - .../r/quicksight_data_source.html.markdown | 176 +- 10 files changed, 1730 insertions(+), 1127 deletions(-) create mode 100644 .changelog/20710.txt create mode 100644 aws/internal/service/quicksight/waiter/status.go create mode 100644 aws/internal/service/quicksight/waiter/waiter.go delete mode 100644 aws/tagsQuickSight.go diff --git a/.changelog/20710.txt b/.changelog/20710.txt new file mode 100644 index 000000000000..aa15b354d6e2 --- /dev/null +++ b/.changelog/20710.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_quicksight_data_source +``` diff --git a/aws/internal/service/quicksight/waiter/status.go b/aws/internal/service/quicksight/waiter/status.go new file mode 100644 index 000000000000..a03b4575a6e2 --- /dev/null +++ b/aws/internal/service/quicksight/waiter/status.go @@ -0,0 +1,31 @@ +package waiter + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/quicksight" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +// DataSourceStatus fetches the DataSource and its Status +func DataSourceStatus(ctx context.Context, conn *quicksight.QuickSight, accountId, datasourceId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + input := &quicksight.DescribeDataSourceInput{ + AwsAccountId: aws.String(accountId), + DataSourceId: aws.String(datasourceId), + } + + output, err := conn.DescribeDataSourceWithContext(ctx, input) + + if err != nil { + return nil, "", err + } + + if output == nil || output.DataSource == nil { + return nil, "", nil + } + + return output.DataSource, aws.StringValue(output.DataSource.Status), nil + } +} diff --git a/aws/internal/service/quicksight/waiter/waiter.go b/aws/internal/service/quicksight/waiter/waiter.go new file mode 100644 index 000000000000..a5085c7bd40c --- /dev/null +++ b/aws/internal/service/quicksight/waiter/waiter.go @@ -0,0 +1,61 @@ +package waiter + +import ( + "context" + "fmt" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/quicksight" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" +) + +const ( + DataSourceCreateTimeout = 5 * time.Minute + DataSourceUpdateTimeout = 5 * time.Minute +) + +// DataSourceCreated waits for a DataSource to return CREATION_SUCCESSFUL +func DataSourceCreated(ctx context.Context, conn *quicksight.QuickSight, accountId, dataSourceId string) (*quicksight.DataSource, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{quicksight.ResourceStatusCreationInProgress}, + Target: []string{quicksight.ResourceStatusCreationSuccessful}, + Refresh: DataSourceStatus(ctx, conn, accountId, dataSourceId), + Timeout: DataSourceCreateTimeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*quicksight.DataSource); ok { + if status, errorInfo := aws.StringValue(output.Status), output.ErrorInfo; status == quicksight.ResourceStatusCreationFailed && errorInfo != nil { + tfresource.SetLastError(err, fmt.Errorf("%s: %s", aws.StringValue(errorInfo.Type), aws.StringValue(errorInfo.Message))) + } + + return output, err + } + + return nil, err +} + +// DataSourceUpdated waits for a DataSource to return UPDATE_SUCCESSFUL +func DataSourceUpdated(ctx context.Context, conn *quicksight.QuickSight, accountId, dataSourceId string) (*quicksight.DataSource, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{quicksight.ResourceStatusUpdateInProgress}, + Target: []string{quicksight.ResourceStatusUpdateSuccessful}, + Refresh: DataSourceStatus(ctx, conn, accountId, dataSourceId), + Timeout: DataSourceUpdateTimeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*quicksight.DataSource); ok { + if status, errorInfo := aws.StringValue(output.Status), output.ErrorInfo; status == quicksight.ResourceStatusUpdateFailed && errorInfo != nil { + tfresource.SetLastError(err, fmt.Errorf("%s: %s", aws.StringValue(errorInfo.Type), aws.StringValue(errorInfo.Message))) + } + + return output, err + } + + return nil, err +} diff --git a/aws/provider.go b/aws/provider.go index 2fb1e56533fa..305a454b300b 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -988,7 +988,7 @@ func Provider() *schema.Provider { "aws_prometheus_workspace": resourceAwsPrometheusWorkspace(), "aws_proxy_protocol_policy": resourceAwsProxyProtocolPolicy(), "aws_qldb_ledger": resourceAwsQLDBLedger(), - "aws_quicksight_data_source" resourceAwsQuickSightDataSource(), + "aws_quicksight_data_source": resourceAwsQuickSightDataSource(), "aws_quicksight_group": resourceAwsQuickSightGroup(), "aws_quicksight_group_membership": resourceAwsQuickSightGroupMembership(), "aws_quicksight_user": resourceAwsQuickSightUser(), diff --git a/aws/resource_aws_quicksight_data_source.go b/aws/resource_aws_quicksight_data_source.go index a92960f47db8..ab7f9c49e285 100644 --- a/aws/resource_aws_quicksight_data_source.go +++ b/aws/resource_aws_quicksight_data_source.go @@ -5,14 +5,15 @@ import ( "fmt" "log" "strings" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/quicksight" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/quicksight/waiter" ) func resourceAwsQuickSightDataSource() *schema.Resource { @@ -33,10 +34,11 @@ func resourceAwsQuickSightDataSource() *schema.Resource { }, "aws_account_id": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validateAwsAccountId, }, "credentials": { @@ -45,26 +47,39 @@ func resourceAwsQuickSightDataSource() *schema.Resource { MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "copy_source_arn": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateArn, + ConflictsWith: []string{"credentials.0.credential_pair"}, + }, "credential_pair": { Type: schema.TypeList, - Required: true, + Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "password": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.NoZeroValues, - Sensitive: true, + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.NoZeroValues, + validation.StringLenBetween(1, 1024), + ), + Sensitive: true, }, "username": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.NoZeroValues, - Sensitive: true, + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.NoZeroValues, + validation.StringLenBetween(1, 64), + ), + Sensitive: true, }, }, }, + ConflictsWith: []string{"credentials.0.copy_source_arn"}, }, }, }, @@ -77,9 +92,12 @@ func resourceAwsQuickSightDataSource() *schema.Resource { }, "name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.NoZeroValues, + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.NoZeroValues, + validation.StringLenBetween(1, 128), + ), }, "parameters": { @@ -241,6 +259,30 @@ func resourceAwsQuickSightDataSource() *schema.Resource { }, }, }, + "oracle": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "host": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "port": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + }, + }, + }, "postgresql": { Type: schema.TypeList, Optional: true, @@ -272,8 +314,9 @@ func resourceAwsQuickSightDataSource() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "catalog": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, }, "host": { Type: schema.TypeString, @@ -288,7 +331,25 @@ func resourceAwsQuickSightDataSource() *schema.Resource { }, }, }, - // The documentation is not clear on how to pass RDS parameters... + "rds": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "database": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "instance_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + }, + }, + }, "redshift": { Type: schema.TypeList, Optional: true, @@ -375,8 +436,9 @@ func resourceAwsQuickSightDataSource() *schema.Resource { ValidateFunc: validation.NoZeroValues, }, "warehouse": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, }, }, }, @@ -388,8 +450,9 @@ func resourceAwsQuickSightDataSource() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "host": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, }, "port": { Type: schema.TypeInt, @@ -471,21 +534,23 @@ func resourceAwsQuickSightDataSource() *schema.Resource { }, "permission": { - Type: schema.TypeList, + Type: schema.TypeSet, Optional: true, MinItems: 1, + MaxItems: 64, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "actions": { Type: schema.TypeSet, Required: true, Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, + MinItems: 1, + MaxItems: 16, }, "principal": { Type: schema.TypeString, Required: true, - ValidateFunc: validation.NoZeroValues, + ValidateFunc: validateArn, }, }, }, @@ -499,7 +564,7 @@ func resourceAwsQuickSightDataSource() *schema.Resource { Schema: map[string]*schema.Schema{ "disable_ssl": { Type: schema.TypeBool, - Optional: true, + Required: true, }, }, }, @@ -507,10 +572,13 @@ func resourceAwsQuickSightDataSource() *schema.Resource { "tags": tagsSchema(), - // This will be inferred from the passed `parameters` value + "tags_all": tagsSchemaComputed(), + "type": { - Type: schema.TypeString, - Computed: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(quicksight.DataSourceType_Values(), false), }, "vpc_connection_properties": { @@ -520,19 +588,22 @@ func resourceAwsQuickSightDataSource() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "vpc_connection_arn": { - Type: schema.TypeBool, - Optional: true, + Type: schema.TypeString, + Required: true, ValidateFunc: validateArn, }, }, }, }, }, + CustomizeDiff: SetTagsDiff, } } func resourceAwsQuickSightDataSourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).quicksightconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{}))) awsAccountId := meta.(*AWSClient).accountid id := d.Get("data_source_id").(string) @@ -542,58 +613,51 @@ func resourceAwsQuickSightDataSourceCreate(ctx context.Context, d *schema.Resour } params := &quicksight.CreateDataSourceInput{ - AwsAccountId: aws.String(awsAccountId), - DataSourceId: aws.String(id), - Name: aws.String(d.Get("name").(string)), - } - - if credentials := resourceAwsQuickSightDataSourceCredentials(d); credentials != nil { - params.Credentials = credentials + AwsAccountId: aws.String(awsAccountId), + DataSourceId: aws.String(id), + DataSourceParameters: expandQuickSightDataSourceParameters(d.Get("parameters").([]interface{})), + Name: aws.String(d.Get("name").(string)), + Type: aws.String(d.Get("type").(string)), } - if dataSourceType, dataSourceParameters := resourceAwsQuickSightDataSourceParameters(d); dataSourceParameters != nil { - params.Type = dataSourceType - params.DataSourceParameters = dataSourceParameters + if len(tags) > 0 { + params.Tags = tags.IgnoreAws().QuicksightTags() } - if v := d.Get("permission"); v != nil && len(v.([]interface{})) != 0 { - params.Permissions = make([]*quicksight.ResourcePermission, 0) - - for _, v := range v.([]interface{}) { - permissionResource := v.(map[string]interface{}) - permission := &quicksight.ResourcePermission{ - Actions: expandStringSet(permissionResource["actions"].(*schema.Set)), - Principal: aws.String(permissionResource["principal"].(string)), - } - - params.Permissions = append(params.Permissions, permission) - } + if v, ok := d.GetOk("credentials"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + params.Credentials = expandQuickSightDataSourceCredentials(v.([]interface{})) } - if sslProperties := resourceAwsQuickSightDataSourceSslProperties(d); sslProperties != nil { - params.SslProperties = sslProperties + if v, ok := d.GetOk("permission"); ok && v.(*schema.Set).Len() > 0 { + params.Permissions = expandQuickSightDataSourcePermissions(v.(*schema.Set).List()) } - if v, ok := d.GetOk("tags"); ok { - params.Tags = tagsFromMapQuickSight(v.(map[string]interface{})) + if v, ok := d.GetOk("ssl_properties"); ok && len(v.([]interface{})) != 0 && v.([]interface{})[0] != nil { + params.SslProperties = expandQuickSightDataSourceSslProperties(v.([]interface{})) } - if vpcConnectionProperties := resourceAwsQuickSightDataSourceVpcConnectionProperties(d); vpcConnectionProperties != nil { - params.VpcConnectionProperties = vpcConnectionProperties + if v, ok := d.GetOk("vpc_connection_properties"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + params.VpcConnectionProperties = expandQuickSightDataSourceVpcConnectionProperties(v.([]interface{})) } - _, err := conn.CreateDataSource(params) + _, err := conn.CreateDataSourceWithContext(ctx, params) if err != nil { return diag.Errorf("error creating QuickSight Data Source: %s", err) } d.SetId(fmt.Sprintf("%s/%s", awsAccountId, id)) + if _, err := waiter.DataSourceCreated(ctx, conn, awsAccountId, id); err != nil { + return diag.Errorf("error waiting from QuickSight Data Source (%s) creation: %s", d.Id(), err) + } + return resourceAwsQuickSightDataSourceRead(ctx, d, meta) } func resourceAwsQuickSightDataSourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).quicksightconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig awsAccountId, dataSourceId, err := resourceAwsQuickSightDataSourceParseID(d.Id()) if err != nil { @@ -605,276 +669,162 @@ func resourceAwsQuickSightDataSourceRead(ctx context.Context, d *schema.Resource DataSourceId: aws.String(dataSourceId), } - var dataSourceResp *quicksight.DescribeDataSourceOutput - err = resource.Retry(5*time.Minute, func() *resource.RetryError { - var err error - dataSourceResp, err = conn.DescribeDataSource(descOpts) - - if dataSourceResp != nil && dataSourceResp.DataSource != nil { - status := aws.StringValue(dataSourceResp.DataSource.Status) - - if status == quicksight.ResourceStatusCreationInProgress || status == quicksight.ResourceStatusUpdateInProgress { - return resource.RetryableError(fmt.Errorf("Data Source operation still in progress (%s): %s", d.Id(), status)) - } - if status == quicksight.ResourceStatusCreationFailed || status == quicksight.ResourceStatusUpdateFailed { - return resource.NonRetryableError(fmt.Errorf("Data Source operation failed (%s): %s", d.Id(), status)) - } - } - - if err != nil { - return resource.NonRetryableError(err) - } - - return nil - }) + output, err := conn.DescribeDataSourceWithContext(ctx, descOpts) - if isAWSErr(err, quicksight.ErrCodeResourceNotFoundException, "") { - log.Printf("[WARN] QuickSight Data Source %s is already gone", d.Id()) + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, quicksight.ErrCodeResourceNotFoundException) { + log.Printf("[WARN] QuickSight Data Source (%s) not found, removing from state", d.Id()) d.SetId("") return nil } + if err != nil { return diag.Errorf("error describing QuickSight Data Source (%s): %s", d.Id(), err) } - permsResp, err := conn.DescribeDataSourcePermissions(&quicksight.DescribeDataSourcePermissionsInput{ - AwsAccountId: aws.String(awsAccountId), - DataSourceId: aws.String(dataSourceId), - }) - - if err != nil { - return diag.Errorf("error describing QuickSight Data Source permissions (%s): %s", d.Id(), err) + if output == nil || output.DataSource == nil { + return diag.Errorf("error describing QuickSight Data Source (%s): empty output", d.Id()) } - dataSource := dataSourceResp.DataSource + dataSource := output.DataSource d.Set("arn", dataSource.Arn) - d.Set("name", dataSource.Name) - d.Set("data_source_id", dataSource.DataSourceId) d.Set("aws_account_id", awsAccountId) + d.Set("data_source_id", dataSource.DataSourceId) + d.Set("name", dataSource.Name) - if err := d.Set("permission", flattenQuickSightPermissions(permsResp.Permissions)); err != nil { - return diag.Errorf("error setting permission error: %#v", err) + if err := d.Set("parameters", flattenQuickSightParameters(dataSource.DataSourceParameters)); err != nil { + return diag.Errorf("error setting parameters: %s", err) + } + + if err := d.Set("ssl_properties", flattenQuickSightSslProperties(dataSource.SslProperties)); err != nil { + return diag.Errorf("error setting ssl_properties: %s", err) } - params := map[string]interface{}{} + tags, err := keyvaluetags.QuicksightListTags(conn, d.Get("arn").(string)) - if dataSource.DataSourceParameters.AmazonElasticsearchParameters != nil { - params = map[string]interface{}{ - "amazon_elasticsearch": []interface{}{ - map[string]interface{}{ - "domain": dataSource.DataSourceParameters.AmazonElasticsearchParameters.Domain, - }, - }, - } + if err != nil { + return diag.Errorf("error listing tags for QuickSight Data Source (%s): %s", d.Id(), err) } - if dataSource.DataSourceParameters.AthenaParameters != nil { - params = map[string]interface{}{ - "athena": []interface{}{ - map[string]interface{}{ - "work_group": dataSource.DataSourceParameters.AthenaParameters.WorkGroup, - }, - }, - } + tags = tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.Errorf("error setting tags: %s", err) } - if dataSource.DataSourceParameters.AuroraParameters != nil { - params = map[string]interface{}{ - "aurora": []interface{}{ - map[string]interface{}{ - "database": dataSource.DataSourceParameters.AuroraParameters.Database, - "host": dataSource.DataSourceParameters.AuroraParameters.Host, - "port": dataSource.DataSourceParameters.AuroraParameters.Port, - }, - }, - } + if err := d.Set("tags_all", tags.Map()); err != nil { + return diag.Errorf("error setting tags_all: %s", err) } - if dataSource.DataSourceParameters.AuroraPostgreSqlParameters != nil { - params = map[string]interface{}{ - "aurora_postgresql": []interface{}{ - map[string]interface{}{ - "database": dataSource.DataSourceParameters.AuroraPostgreSqlParameters.Database, - "host": dataSource.DataSourceParameters.AuroraPostgreSqlParameters.Host, - "port": dataSource.DataSourceParameters.AuroraPostgreSqlParameters.Port, - }, - }, - } + d.Set("type", dataSource.Type) + + if err := d.Set("vpc_connection_properties", flattenQuickSightVpcConnectionProperties(dataSource.VpcConnectionProperties)); err != nil { + return diag.Errorf("error setting vpc_connection_properties: %s", err) } - if dataSource.DataSourceParameters.AwsIotAnalyticsParameters != nil { - params = map[string]interface{}{ - "aws_iot_analytics": []interface{}{ - map[string]interface{}{ - "data_set_name": dataSource.DataSourceParameters.AwsIotAnalyticsParameters.DataSetName, - }, - }, - } + permsResp, err := conn.DescribeDataSourcePermissionsWithContext(ctx, &quicksight.DescribeDataSourcePermissionsInput{ + AwsAccountId: aws.String(awsAccountId), + DataSourceId: aws.String(dataSourceId), + }) + + if err != nil { + return diag.Errorf("error describing QuickSight Data Source (%s) Permissions: %s", d.Id(), err) } - if dataSource.DataSourceParameters.JiraParameters != nil { - params = map[string]interface{}{ - "jira": []interface{}{ - map[string]interface{}{ - "site_base_url": dataSource.DataSourceParameters.JiraParameters.SiteBaseUrl, - }, - }, - } + if err := d.Set("permission", flattenQuickSightPermissions(permsResp.Permissions)); err != nil { + return diag.Errorf("error setting permission: %s", err) } - if dataSource.DataSourceParameters.MariaDbParameters != nil { - params = map[string]interface{}{ - "maria_db": []interface{}{ - map[string]interface{}{ - "database": dataSource.DataSourceParameters.MariaDbParameters.Database, - "host": dataSource.DataSourceParameters.MariaDbParameters.Host, - "port": dataSource.DataSourceParameters.MariaDbParameters.Port, - }, - }, + return nil +} + +func resourceAwsQuickSightDataSourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*AWSClient).quicksightconn + + if d.HasChangesExcept("permission", "tags", "tags_all") { + awsAccountId, dataSourceId, err := resourceAwsQuickSightDataSourceParseID(d.Id()) + if err != nil { + return diag.FromErr(err) } - } - if dataSource.DataSourceParameters.MySqlParameters != nil { - params = map[string]interface{}{ - "mysql": []interface{}{ - map[string]interface{}{ - "database": dataSource.DataSourceParameters.MySqlParameters.Database, - "host": dataSource.DataSourceParameters.MySqlParameters.Host, - "port": dataSource.DataSourceParameters.MySqlParameters.Port, - }, - }, + params := &quicksight.UpdateDataSourceInput{ + AwsAccountId: aws.String(awsAccountId), + DataSourceId: aws.String(dataSourceId), + Name: aws.String(d.Get("name").(string)), } - } - if dataSource.DataSourceParameters.PostgreSqlParameters != nil { - params = map[string]interface{}{ - "postgresql": []interface{}{ - map[string]interface{}{ - "database": dataSource.DataSourceParameters.PostgreSqlParameters.Database, - "host": dataSource.DataSourceParameters.PostgreSqlParameters.Host, - "port": dataSource.DataSourceParameters.PostgreSqlParameters.Port, - }, - }, + if d.HasChange("credentials") { + params.Credentials = expandQuickSightDataSourceCredentials(d.Get("credentials").([]interface{})) } - } - if dataSource.DataSourceParameters.PrestoParameters != nil { - params = map[string]interface{}{ - "presto": []interface{}{ - map[string]interface{}{ - "catalog": dataSource.DataSourceParameters.PrestoParameters.Catalog, - "host": dataSource.DataSourceParameters.PrestoParameters.Host, - "port": dataSource.DataSourceParameters.PrestoParameters.Port, - }, - }, + if d.HasChange("parameters") { + params.DataSourceParameters = expandQuickSightDataSourceParameters(d.Get("parameters").([]interface{})) } - } - if dataSource.DataSourceParameters.RedshiftParameters != nil { - params = map[string]interface{}{ - "redshift": []interface{}{ - map[string]interface{}{ - "cluster_id": dataSource.DataSourceParameters.RedshiftParameters.ClusterId, - "database": dataSource.DataSourceParameters.RedshiftParameters.Database, - "host": dataSource.DataSourceParameters.RedshiftParameters.Host, - "port": dataSource.DataSourceParameters.RedshiftParameters.Port, - }, - }, + if d.HasChange("ssl_properties") { + params.SslProperties = expandQuickSightDataSourceSslProperties(d.Get("ssl_properties").([]interface{})) } - } - if dataSource.DataSourceParameters.S3Parameters != nil { - params = map[string]interface{}{ - "s3": []interface{}{ - map[string]interface{}{ - "manifest_file_location": []interface{}{ - map[string]interface{}{ - "bucket": dataSource.DataSourceParameters.S3Parameters.ManifestFileLocation.Bucket, - "key": dataSource.DataSourceParameters.S3Parameters.ManifestFileLocation.Key, - }, - }, - }, - }, + if d.HasChange("vpc_connection_properties") { + params.VpcConnectionProperties = expandQuickSightDataSourceVpcConnectionProperties(d.Get("vpc_connection_properties").([]interface{})) } - } - if dataSource.DataSourceParameters.ServiceNowParameters != nil { - params = map[string]interface{}{ - "service_now": []interface{}{ - map[string]interface{}{ - "site_base_url": dataSource.DataSourceParameters.ServiceNowParameters.SiteBaseUrl, - }, - }, + _, err = conn.UpdateDataSourceWithContext(ctx, params) + + if err != nil { + return diag.Errorf("error updating QuickSight Data Source (%s): %s", d.Id(), err) } - } - if dataSource.DataSourceParameters.SnowflakeParameters != nil { - params = map[string]interface{}{ - "snowflake": []interface{}{ - map[string]interface{}{ - "database": dataSource.DataSourceParameters.SnowflakeParameters.Database, - "host": dataSource.DataSourceParameters.SnowflakeParameters.Host, - "warehouse": dataSource.DataSourceParameters.SnowflakeParameters.Warehouse, - }, - }, + if _, err := waiter.DataSourceUpdated(ctx, conn, awsAccountId, dataSourceId); err != nil { + return diag.Errorf("error waiting for QuickSight Data Source (%s) to update: %s", d.Id(), err) } } - if dataSource.DataSourceParameters.SparkParameters != nil { - params = map[string]interface{}{ - "spark": []interface{}{ - map[string]interface{}{ - "host": dataSource.DataSourceParameters.SparkParameters.Host, - "port": dataSource.DataSourceParameters.SparkParameters.Port, - }, - }, + if d.HasChange("permission") { + awsAccountId, dataSourceId, err := resourceAwsQuickSightDataSourceParseID(d.Id()) + if err != nil { + return diag.FromErr(err) } - } - if dataSource.DataSourceParameters.SqlServerParameters != nil { - params = map[string]interface{}{ - "sql_server": []interface{}{ - map[string]interface{}{ - "database": dataSource.DataSourceParameters.SqlServerParameters.Database, - "host": dataSource.DataSourceParameters.SqlServerParameters.Host, - "port": dataSource.DataSourceParameters.SqlServerParameters.Port, - }, - }, + oraw, nraw := d.GetChange("permission") + o := oraw.(*schema.Set).List() + n := nraw.(*schema.Set).List() + + toGrant, toRevoke := diffQuickSightDataSourcePermissions(o, n) + + params := &quicksight.UpdateDataSourcePermissionsInput{ + AwsAccountId: aws.String(awsAccountId), + DataSourceId: aws.String(dataSourceId), } - } - if dataSource.DataSourceParameters.TeradataParameters != nil { - params = map[string]interface{}{ - "teradata": []interface{}{ - map[string]interface{}{ - "database": dataSource.DataSourceParameters.TeradataParameters.Database, - "host": dataSource.DataSourceParameters.TeradataParameters.Host, - "port": dataSource.DataSourceParameters.TeradataParameters.Port, - }, - }, + if len(toGrant) > 0 { + params.GrantPermissions = toGrant } - } - if dataSource.DataSourceParameters.TwitterParameters != nil { - params = map[string]interface{}{ - "twitter": []interface{}{ - map[string]interface{}{ - "max_rows": dataSource.DataSourceParameters.TwitterParameters.MaxRows, - "query": dataSource.DataSourceParameters.TwitterParameters.Query, - }, - }, + if len(toRevoke) > 0 { + params.RevokePermissions = toRevoke + } + + _, err = conn.UpdateDataSourcePermissionsWithContext(ctx, params) + + if err != nil { + return diag.Errorf("error updating QuickSight Data Source (%s) permissions: %s", dataSourceId, err) } } - d.Set("parameters", []interface{}{params}) + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") - d.Set("type", inferQuickSightDataSourceTypeFromKey(params)) + if err := keyvaluetags.QuicksightUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return diag.Errorf("error updating QuickSight Data Source (%s) tags: %s", d.Id(), err) + } + } - return nil + return resourceAwsQuickSightDataSourceRead(ctx, d, meta) } -func resourceAwsQuickSightDataSourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceAwsQuickSightDataSourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).quicksightconn awsAccountId, dataSourceId, err := resourceAwsQuickSightDataSourceParseID(d.Id()) @@ -882,443 +832,865 @@ func resourceAwsQuickSightDataSourceUpdate(ctx context.Context, d *schema.Resour return diag.FromErr(err) } - params := &quicksight.UpdateDataSourceInput{ + deleteOpts := &quicksight.DeleteDataSourceInput{ AwsAccountId: aws.String(awsAccountId), DataSourceId: aws.String(dataSourceId), } - if credentials := resourceAwsQuickSightDataSourceCredentials(d); credentials != nil { - params.Credentials = credentials + _, err = conn.DeleteDataSourceWithContext(ctx, deleteOpts) + + if tfawserr.ErrCodeEquals(err, quicksight.ErrCodeResourceNotFoundException) { + return nil } - if dataSourceType, dataSourceParameters := resourceAwsQuickSightDataSourceParameters(d); dataSourceParameters != nil { - params.DataSourceParameters = dataSourceParameters - d.Set("type", dataSourceType) + if err != nil { + return diag.Errorf("error deleting QuickSight Data Source (%s): %s", d.Id(), err) } - if d.HasChange("permission") { - oraw, nraw := d.GetChange("permission") - o := oraw.([]interface{}) - n := nraw.([]interface{}) - toGrant, toRevoke := diffQuickSightPermissionsToGrantAndRevoke(o, n) - - if len(toGrant) > 0 || len(toRevoke) > 0 { - params := &quicksight.UpdateDataSourcePermissionsInput{ - AwsAccountId: aws.String(awsAccountId), - DataSourceId: aws.String(dataSourceId), - GrantPermissions: toGrant, - RevokePermissions: toRevoke, - } + return nil +} - _, err := conn.UpdateDataSourcePermissions(params) - if err != nil { - return diag.Errorf("error updating QuickSight Data Source (%s) permissions: %s", dataSourceId, err) - } - } +func expandQuickSightDataSourceCredentials(tfList []interface{}) *quicksight.DataSourceCredentials { + if len(tfList) == 0 || tfList[0] == nil { + return nil } - if sslProperties := resourceAwsQuickSightDataSourceSslProperties(d); sslProperties != nil { - params.SslProperties = sslProperties + tfMap, ok := tfList[0].(map[string]interface{}) + + if !ok { + return nil } - if d.HasChange("tags") { - oraw, nraw := d.GetChange("tags") - o := oraw.(map[string]interface{}) - n := nraw.(map[string]interface{}) - c, r := diffTagsQuickSight(tagsFromMapQuickSight(o), tagsFromMapQuickSight(n)) + credentials := &quicksight.DataSourceCredentials{} - if len(r) > 0 { - _, err := conn.UntagResource(&quicksight.UntagResourceInput{ - ResourceArn: aws.String(quicksightDataSourceArn(meta.(*AWSClient).region, awsAccountId, dataSourceId)), - TagKeys: tagKeysQuickSight(r), - }) - if err != nil { - return diag.Errorf("error deleting QuickSight Data Source (%s) tags: %s", d.Id(), err) - } - } + if v, ok := tfMap["copy_source_arn"].(string); ok && v != "" { + credentials.CopySourceArn = aws.String(v) + } - if len(c) > 0 { - _, err := conn.TagResource(&quicksight.TagResourceInput{ - ResourceArn: aws.String(quicksightDataSourceArn(meta.(*AWSClient).region, awsAccountId, dataSourceId)), - Tags: c, - }) - if err != nil { - return diag.Errorf("error updating QuickSight Data Source (%s) tags: %s", d.Id(), err) - } - } + if v, ok := tfMap["credential_pair"].([]interface{}); ok && len(v) > 0 { + credentials.CredentialPair = expandQuickSightDataSourceCredentialPair(v) } - if vpcConnectionProperties := resourceAwsQuickSightDataSourceVpcConnectionProperties(d); vpcConnectionProperties != nil { - params.VpcConnectionProperties = vpcConnectionProperties + return credentials +} + +func expandQuickSightDataSourceCredentialPair(tfList []interface{}) *quicksight.CredentialPair { + if len(tfList) == 0 || tfList[0] == nil { + return nil } - _, err = conn.UpdateDataSource(params) - if isAWSErr(err, quicksight.ErrCodeResourceNotFoundException, "") { - log.Printf("[WARN] QuickSight Data Source %s is already gone", d.Id()) - d.SetId("") + credentialPair := &quicksight.CredentialPair{} + + tfMap, ok := tfList[0].(map[string]interface{}) + + if !ok { return nil } - if err != nil { - return diag.Errorf("error updating QuickSight Data Source (%s): %s", d.Id(), err) + + if v, ok := tfMap["username"].(string); ok && v != "" { + credentialPair.Username = aws.String(v) } - return resourceAwsQuickSightDataSourceRead(ctx, d, meta) -} + if v, ok := tfMap["password"].(string); ok && v != "" { + credentialPair.Password = aws.String(v) + } -func resourceAwsQuickSightDataSourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - conn := meta.(*AWSClient).quicksightconn + return credentialPair +} - awsAccountId, dataSourceId, err := resourceAwsQuickSightDataSourceParseID(d.Id()) - if err != nil { - return diag.FromErr(err) +func expandQuickSightDataSourceParameters(tfList []interface{}) *quicksight.DataSourceParameters { + if len(tfList) == 0 || tfList[0] == nil { + return nil } - deleteOpts := &quicksight.DeleteDataSourceInput{ - AwsAccountId: aws.String(awsAccountId), - DataSourceId: aws.String(dataSourceId), + tfMap, ok := tfList[0].(map[string]interface{}) + + if !ok { + return nil } - if _, err := conn.DeleteDataSource(deleteOpts); err != nil { - if isAWSErr(err, quicksight.ErrCodeResourceNotFoundException, "") { - return nil + dataSourceParams := &quicksight.DataSourceParameters{} + + if v, ok := tfMap["amazon_elasticsearch"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + m, ok := v[0].(map[string]interface{}) + + if ok { + ps := &quicksight.AmazonElasticsearchParameters{} + + if v, ok := m["domain"].(string); ok && v != "" { + ps.Domain = aws.String(v) + } + + dataSourceParams.AmazonElasticsearchParameters = ps } - return diag.Errorf("error deleting QuickSight Data Source (%s): %s", d.Id(), err) } - return nil -} + if v := tfMap["athena"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + m, ok := v[0].(map[string]interface{}) -func resourceAwsQuickSightDataSourceCredentials(d *schema.ResourceData) *quicksight.DataSourceCredentials { - if v := d.Get("credentials"); v != nil { - for _, v := range v.([]interface{}) { - credentials := v.(map[string]interface{}) + if ok { + ps := &quicksight.AthenaParameters{} + if v, ok := m["work_group"].(string); ok && v != "" { + ps.WorkGroup = aws.String(v) + } - if v := credentials["credential_pair"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - credentialPairResource := v.(map[string]interface{}) - credentialPair := &quicksight.CredentialPair{} + dataSourceParams.AthenaParameters = ps + } + } - if v, ok := credentialPairResource["username"]; ok && v.(string) != "" { - credentialPair.Username = aws.String(v.(string)) - } + if v := tfMap["aurora"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + m, ok := v[0].(map[string]interface{}) - if v, ok := credentialPairResource["password"]; ok && v.(string) != "" { - credentialPair.Password = aws.String(v.(string)) - } + if ok { + ps := &quicksight.AuroraParameters{} + if v, ok := m["database"].(string); ok && v != "" { + ps.Database = aws.String(v) + } + if v, ok := m["host"].(string); ok && v != "" { + ps.Host = aws.String(v) + } + if v, ok := m["port"].(int); ok { + ps.Port = aws.Int64(int64(v)) + } - return &quicksight.DataSourceCredentials{ - CredentialPair: credentialPair, - } - } + dataSourceParams.AuroraParameters = ps + } + } + + if v, ok := tfMap["aurora_postgresql"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + m, ok := v[0].(map[string]interface{}) + + if ok { + ps := &quicksight.AuroraPostgreSqlParameters{} + if v, ok := m["database"].(string); ok && v != "" { + ps.Database = aws.String(v) + } + if v, ok := m["host"].(string); ok && v != "" { + ps.Host = aws.String(v) + } + if v, ok := m["port"].(int); ok { + ps.Port = aws.Int64(int64(v)) } + + dataSourceParams.AuroraPostgreSqlParameters = ps } } - return nil -} + if v := tfMap["aws_iot_analytics"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + m, ok := v[0].(map[string]interface{}) -var quickSightDataSourceParamToDataType = map[string]string{ - "amazon_elasticsearch": quicksight.DataSourceTypeAmazonElasticsearch, - "athena": quicksight.DataSourceTypeAthena, - "aurora": quicksight.DataSourceTypeAurora, - "aurora_postgresql": quicksight.DataSourceTypeAuroraPostgresql, - "aws_iot_analytics": quicksight.DataSourceTypeAwsIotAnalytics, - "jira": quicksight.DataSourceTypeJira, - "maria_db": quicksight.DataSourceTypeMariadb, - "mysql": quicksight.DataSourceTypeMysql, - "postgresql": quicksight.DataSourceTypePostgresql, - "presto": quicksight.DataSourceTypePresto, - "redshift": quicksight.DataSourceTypeRedshift, - "s3": quicksight.DataSourceTypeS3, - "service_now": quicksight.DataSourceTypeServicenow, - "snowflake": quicksight.DataSourceTypeSnowflake, - "spark": quicksight.DataSourceTypeSpark, - "sql_server": quicksight.DataSourceTypeSqlserver, - "teradata": quicksight.DataSourceTypeTeradata, - "twitter": quicksight.DataSourceTypeTwitter, -} + if ok { + ps := &quicksight.AwsIotAnalyticsParameters{} + if v, ok := m["data_set_name"].(string); ok && v != "" { + ps.DataSetName = aws.String(v) + } + + dataSourceParams.AwsIotAnalyticsParameters = ps + } + } + + if v := tfMap["jira"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + m, ok := v[0].(map[string]interface{}) -func inferQuickSightDataSourceTypeFromKey(params map[string]interface{}) string { - if len(params) == 1 { - for k := range params { - if dataSourceType, found := quickSightDataSourceParamToDataType[k]; found { - return dataSourceType + if ok { + ps := &quicksight.JiraParameters{} + if v, ok := m["site_base_url"].(string); ok && v != "" { + ps.SiteBaseUrl = aws.String(v) } + + dataSourceParams.JiraParameters = ps } } - for k, v := range params { - if dataSourceType, found := quickSightDataSourceParamToDataType[k]; found && v.([]interface{}) != nil && len(v.([]interface{})) > 0 { - return dataSourceType + if v := tfMap["maria_db"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + m, ok := v[0].(map[string]interface{}) + + if ok { + ps := &quicksight.MariaDbParameters{} + if v, ok := m["database"].(string); ok && v != "" { + ps.Database = aws.String(v) + } + if v, ok := m["host"].(string); ok && v != "" { + ps.Host = aws.String(v) + } + if v, ok := m["port"].(int); ok { + ps.Port = aws.Int64(int64(v)) + } + + dataSourceParams.MariaDbParameters = ps } } - return "UNKNOWN" -} + if v := tfMap["mysql"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + m, ok := v[0].(map[string]interface{}) -func resourceAwsQuickSightDataSourceParameters(d *schema.ResourceData) (*string, *quicksight.DataSourceParameters) { - if v := d.Get("parameters"); v != nil { - dataSourceParamsResource := &quicksight.DataSourceParameters{} - var dataSourceType string - for _, v := range v.([]interface{}) { - dataSourceParams := v.(map[string]interface{}) - dataSourceType = inferQuickSightDataSourceTypeFromKey(dataSourceParams) - - if v := dataSourceParams["amazon_elasticsearch"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - psResource := v.(map[string]interface{}) - dataSourceParamsResource.AmazonElasticsearchParameters = &quicksight.AmazonElasticsearchParameters{ - Domain: aws.String(psResource["domain"].(string)), - } - } + if ok { + ps := &quicksight.MySqlParameters{} + if v, ok := m["database"].(string); ok && v != "" { + ps.Database = aws.String(v) + } + if v, ok := m["host"].(string); ok && v != "" { + ps.Host = aws.String(v) + } + if v, ok := m["port"].(int); ok { + ps.Port = aws.Int64(int64(v)) } - if v := dataSourceParams["athena"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - psResource := v.(map[string]interface{}) - ps := &quicksight.AthenaParameters{} + dataSourceParams.MySqlParameters = ps + } + } - if v, ok := psResource["work_group"]; ok && v.(string) != "" { - ps.WorkGroup = aws.String(v.(string)) - } + if v := tfMap["oracle"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + m, ok := v[0].(map[string]interface{}) - dataSourceParamsResource.AthenaParameters = ps - } + if ok { + ps := &quicksight.OracleParameters{} + if v, ok := m["database"].(string); ok && v != "" { + ps.Database = aws.String(v) + } + if v, ok := m["host"].(string); ok && v != "" { + ps.Host = aws.String(v) + } + if v, ok := m["port"].(int); ok { + ps.Port = aws.Int64(int64(v)) } - if v := dataSourceParams["aurora"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - psResource := v.(map[string]interface{}) - dataSourceParamsResource.AuroraParameters = &quicksight.AuroraParameters{ - Database: aws.String(psResource["database"].(string)), - Host: aws.String(psResource["host"].(string)), - Port: aws.Int64(psResource["port"].(int64)), - } - } + dataSourceParams.OracleParameters = ps + } + } + + if v := tfMap["postgresql"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + m, ok := v[0].(map[string]interface{}) + + if ok { + ps := &quicksight.PostgreSqlParameters{} + if v, ok := m["database"].(string); ok && v != "" { + ps.Database = aws.String(v) + } + if v, ok := m["host"].(string); ok && v != "" { + ps.Host = aws.String(v) + } + if v, ok := m["port"].(int); ok { + ps.Port = aws.Int64(int64(v)) } - if v := dataSourceParams["aurora_postgresql"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - psResource := v.(map[string]interface{}) - dataSourceParamsResource.AuroraPostgreSqlParameters = &quicksight.AuroraPostgreSqlParameters{ - Database: aws.String(psResource["database"].(string)), - Host: aws.String(psResource["host"].(string)), - Port: aws.Int64(psResource["port"].(int64)), - } - } + dataSourceParams.PostgreSqlParameters = ps + } + } + if v := tfMap["presto"].([]interface{}); ok && len(v) > 0 && v != nil { + m, ok := v[0].(map[string]interface{}) + + if ok { + ps := &quicksight.PrestoParameters{} + if v, ok := m["catalog"].(string); ok && v != "" { + ps.Catalog = aws.String(v) + } + if v, ok := m["host"].(string); ok && v != "" { + ps.Host = aws.String(v) + } + if v, ok := m["port"].(int); ok { + ps.Port = aws.Int64(int64(v)) } - if v := dataSourceParams["aws_iot_analytics"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - psResource := v.(map[string]interface{}) - dataSourceParamsResource.AwsIotAnalyticsParameters = &quicksight.AwsIotAnalyticsParameters{ - DataSetName: aws.String(psResource["data_set_name"].(string)), - } - } + dataSourceParams.PrestoParameters = ps + } + } + + if v := tfMap["rds"].([]interface{}); ok && len(v) > 0 && v != nil { + m, ok := v[0].(map[string]interface{}) + + if ok { + ps := &quicksight.RdsParameters{} + + if v, ok := m["database"].(string); ok && v != "" { + ps.Database = aws.String(v) + } + if v, ok := m["instance_id"].(string); ok && v != "" { + ps.InstanceId = aws.String(v) } - if v := dataSourceParams["jira"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - psResource := v.(map[string]interface{}) - dataSourceParamsResource.JiraParameters = &quicksight.JiraParameters{ - SiteBaseUrl: aws.String(psResource["site_base_url"].(string)), - } - } + dataSourceParams.RdsParameters = ps + } + } + + if v := tfMap["redshift"].([]interface{}); ok && len(v) > 0 && v != nil { + m, ok := v[0].(map[string]interface{}) + + if ok { + ps := &quicksight.RedshiftParameters{} + if v, ok := m["cluster_id"].(string); ok && v != "" { + ps.ClusterId = aws.String(v) + } + if v, ok := m["database"].(string); ok && v != "" { + ps.Database = aws.String(v) } + if v, ok := m["host"].(string); ok && v != "" { + ps.Host = aws.String(v) + } + if v, ok := m["port"].(int); ok { + ps.Port = aws.Int64(int64(v)) + } + + dataSourceParams.RedshiftParameters = ps + } + } + + if v := tfMap["s3"].([]interface{}); ok && len(v) > 0 && v != nil { + m, ok := v[0].(map[string]interface{}) - if v := dataSourceParams["maria_db"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - psResource := v.(map[string]interface{}) - dataSourceParamsResource.MariaDbParameters = &quicksight.MariaDbParameters{ - Database: aws.String(psResource["database"].(string)), - Host: aws.String(psResource["host"].(string)), - Port: aws.Int64(psResource["port"].(int64)), + if ok { + ps := &quicksight.S3Parameters{} + if v, ok := m["manifest_file_location"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + lm, ok := v[0].(map[string]interface{}) + if ok { + loc := &quicksight.ManifestFileLocation{} + + if v, ok := lm["bucket"].(string); ok && v != "" { + loc.Bucket = aws.String(v) } - } - } - if v := dataSourceParams["mysql"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - psResource := v.(map[string]interface{}) - dataSourceParamsResource.MySqlParameters = &quicksight.MySqlParameters{ - Database: aws.String(psResource["database"].(string)), - Host: aws.String(psResource["host"].(string)), - Port: aws.Int64(psResource["port"].(int64)), + if v, ok := lm["key"].(string); ok && v != "" { + loc.Key = aws.String(v) } + + ps.ManifestFileLocation = loc } } - if v := dataSourceParams["postgresql"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - psResource := v.(map[string]interface{}) - dataSourceParamsResource.PostgreSqlParameters = &quicksight.PostgreSqlParameters{ - Database: aws.String(psResource["database"].(string)), - Host: aws.String(psResource["host"].(string)), - Port: aws.Int64(psResource["port"].(int64)), - } - } + dataSourceParams.S3Parameters = ps + } + } + + if v := tfMap["service_now"].([]interface{}); ok && len(v) > 0 && v != nil { + m, ok := v[0].(map[string]interface{}) + + if ok { + ps := &quicksight.ServiceNowParameters{} + if v, ok := m["site_base_url"].(string); ok && v != "" { + ps.SiteBaseUrl = aws.String(v) } - if v := dataSourceParams["presto"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - psResource := v.(map[string]interface{}) - dataSourceParamsResource.PrestoParameters = &quicksight.PrestoParameters{ - Catalog: aws.String(psResource["catalog"].(string)), - Host: aws.String(psResource["host"].(string)), - Port: aws.Int64(psResource["port"].(int64)), - } - } + dataSourceParams.ServiceNowParameters = ps + } + } + + if v := tfMap["snowflake"].([]interface{}); ok && len(v) > 0 && v != nil { + m, ok := v[0].(map[string]interface{}) + + if ok { + ps := &quicksight.SnowflakeParameters{} + + if v, ok := m["database"].(string); ok && v != "" { + ps.Database = aws.String(v) + } + if v, ok := m["host"].(string); ok && v != "" { + ps.Host = aws.String(v) + } + if v, ok := m["warehouse"].(string); ok && v != "" { + ps.Warehouse = aws.String(v) } - if v := dataSourceParams["redshift"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - psResource := v.(map[string]interface{}) - ps := &quicksight.RedshiftParameters{ - Database: aws.String(psResource["database"].(string)), - } + dataSourceParams.SnowflakeParameters = ps + } + } - if v, ok := psResource["cluster_id"]; ok && v.(string) != "" { - ps.ClusterId = aws.String(v.(string)) - } + if v := tfMap["spark"].([]interface{}); ok && len(v) > 0 && v != nil { + m, ok := v[0].(map[string]interface{}) - if v, ok := psResource["host"]; ok && v.(string) != "" { - ps.Host = aws.String(v.(string)) - } + if ok { + ps := &quicksight.SparkParameters{} - if v, ok := psResource["port"]; ok && v.(int64) != 0 { - ps.Port = aws.Int64(v.(int64)) - } + if v, ok := m["host"].(string); ok && v != "" { + ps.Host = aws.String(v) + } + if v, ok := m["port"].(int); ok { + ps.Port = aws.Int64(int64(v)) + } - dataSourceParamsResource.RedshiftParameters = ps - } + dataSourceParams.SparkParameters = ps + } + } + + if v := tfMap["sql_server"].([]interface{}); ok && len(v) > 0 && v != nil { + m, ok := v[0].(map[string]interface{}) + + if ok { + ps := &quicksight.SqlServerParameters{} + + if v, ok := m["database"].(string); ok && v != "" { + ps.Database = aws.String(v) + } + if v, ok := m["host"].(string); ok && v != "" { + ps.Host = aws.String(v) + } + if v, ok := m["port"].(int); ok { + ps.Port = aws.Int64(int64(v)) } - if v := dataSourceParams["s3"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - s3 := v.(map[string]interface{}) - if v := s3["manifest_file_location"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - psResource := v.(map[string]interface{}) - dataSourceParamsResource.S3Parameters = &quicksight.S3Parameters{ - ManifestFileLocation: &quicksight.ManifestFileLocation{ - Bucket: aws.String(psResource["bucket"].(string)), - Key: aws.String(psResource["key"].(string)), - }, - } - } - } - } + dataSourceParams.SqlServerParameters = ps + } + } + + if v := tfMap["teradata"].([]interface{}); ok && len(v) > 0 && v != nil { + m, ok := v[0].(map[string]interface{}) + + if ok { + ps := &quicksight.TeradataParameters{} + + if v, ok := m["database"].(string); ok && v != "" { + ps.Database = aws.String(v) + } + if v, ok := m["host"].(string); ok && v != "" { + ps.Host = aws.String(v) + } + if v, ok := m["port"].(int); ok { + ps.Port = aws.Int64(int64(v)) } - if v := dataSourceParams["service_now"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - psResource := v.(map[string]interface{}) - dataSourceParamsResource.ServiceNowParameters = &quicksight.ServiceNowParameters{ - SiteBaseUrl: aws.String(psResource["site_base_url"].(string)), - } - } + dataSourceParams.TeradataParameters = ps + } + } + + if v := tfMap["twitter"].([]interface{}); ok && len(v) > 0 && v != nil { + m, ok := v[0].(map[string]interface{}) + + if ok { + ps := &quicksight.TwitterParameters{} + + if v, ok := m["max_rows"].(int); ok { + ps.MaxRows = aws.Int64(int64(v)) + } + if v, ok := m["query"].(string); ok && v != "" { + ps.Query = aws.String(v) } - if v := dataSourceParams["snowflake"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - psResource := v.(map[string]interface{}) - dataSourceParamsResource.SnowflakeParameters = &quicksight.SnowflakeParameters{ - Database: aws.String(psResource["database"].(string)), - Host: aws.String(psResource["host"].(string)), - Warehouse: aws.String(psResource["warehouse"].(string)), - } - } + dataSourceParams.TwitterParameters = ps + } + } + + return dataSourceParams +} + +func diffQuickSightDataSourcePermissions(o, n []interface{}) ([]*quicksight.ResourcePermission, []*quicksight.ResourcePermission) { + old := expandQuickSightDataSourcePermissions(o) + new := expandQuickSightDataSourcePermissions(n) + + var toGrant, toRevoke []*quicksight.ResourcePermission + + for _, op := range old { + found := false + + for _, np := range new { + if aws.StringValue(np.Principal) != aws.StringValue(op.Principal) { + continue } - if v := dataSourceParams["spark"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - psResource := v.(map[string]interface{}) - dataSourceParamsResource.SparkParameters = &quicksight.SparkParameters{ - Host: aws.String(psResource["host"].(string)), - Port: aws.Int64(psResource["port"].(int64)), - } - } + found = true + newActions := flattenStringSet(np.Actions) + oldActions := flattenStringSet(op.Actions) + + if newActions.Equal(oldActions) { + break } - if v := dataSourceParams["sql_server"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - psResource := v.(map[string]interface{}) - dataSourceParamsResource.SqlServerParameters = &quicksight.SqlServerParameters{ - Database: aws.String(psResource["database"].(string)), - Host: aws.String(psResource["host"].(string)), - Port: aws.Int64(psResource["port"].(int64)), - } - } + toRemove := oldActions.Difference(newActions) + toAdd := newActions.Difference(oldActions) + + if toRemove.Len() > 0 { + toRevoke = append(toRevoke, &quicksight.ResourcePermission{ + Actions: expandStringSet(toRemove), + Principal: np.Principal, + }) } - if v := dataSourceParams["teradata"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - psResource := v.(map[string]interface{}) - dataSourceParamsResource.TeradataParameters = &quicksight.TeradataParameters{ - Database: aws.String(psResource["database"].(string)), - Host: aws.String(psResource["host"].(string)), - Port: aws.Int64(psResource["port"].(int64)), - } - } + if toAdd.Len() > 0 { + toGrant = append(toGrant, &quicksight.ResourcePermission{ + Actions: expandStringSet(toAdd), + Principal: np.Principal, + }) } + } - if v := dataSourceParams["twitter"]; v != nil && v.([]interface{}) != nil { - for _, v := range v.([]interface{}) { - psResource := v.(map[string]interface{}) - dataSourceParamsResource.TwitterParameters = &quicksight.TwitterParameters{ - MaxRows: aws.Int64(psResource["max_rows"].(int64)), - Query: aws.String(psResource["query"].(string)), - } - } + if !found { + toRevoke = append(toRevoke, op) + } + } + + for _, np := range new { + found := false + + for _, op := range old { + if aws.StringValue(np.Principal) == aws.StringValue(op.Principal) { + found = true + break } } - return aws.String(dataSourceType), dataSourceParamsResource + + if !found { + toGrant = append(toGrant, np) + } } - return aws.String(""), nil + return toGrant, toRevoke } -func resourceAwsQuickSightDataSourceSslProperties(d *schema.ResourceData) *quicksight.SslProperties { - if v := d.Get("ssl_properties"); v != nil { - for _, v := range v.([]interface{}) { - sslProperties := v.(map[string]interface{}) +func expandQuickSightDataSourcePermissions(tfList []interface{}) []*quicksight.ResourcePermission { + permissions := make([]*quicksight.ResourcePermission, len(tfList)) - if v, present := sslProperties["disable_ssl"]; present { - return &quicksight.SslProperties{ - DisableSsl: aws.Bool(v.(bool)), - } - } + for i, tfListRaw := range tfList { + tfMap := tfListRaw.(map[string]interface{}) + permission := &quicksight.ResourcePermission{ + Actions: expandStringSet(tfMap["actions"].(*schema.Set)), + Principal: aws.String(tfMap["principal"].(string)), } + + permissions[i] = permission } - return nil + return permissions } -func resourceAwsQuickSightDataSourceVpcConnectionProperties(d *schema.ResourceData) *quicksight.VpcConnectionProperties { - if v := d.Get("vpc_connection_properties"); v != nil { - for _, v := range v.([]interface{}) { - vpcConnectionProperties := v.(map[string]interface{}) +func expandQuickSightDataSourceSslProperties(tfList []interface{}) *quicksight.SslProperties { + if len(tfList) == 0 { + return nil + } - if v := vpcConnectionProperties["vpc_connection_arn"]; v != nil && v.(string) != "" { - return &quicksight.VpcConnectionProperties{ - VpcConnectionArn: aws.String(v.(string)), - } - } + tfMap, ok := tfList[0].(map[string]interface{}) + if !ok { + return nil + } + + props := &quicksight.SslProperties{} + + if v, ok := tfMap["disable_ssl"].(bool); ok { + props.DisableSsl = aws.Bool(v) + } + + return props +} + +func expandQuickSightDataSourceVpcConnectionProperties(tfList []interface{}) *quicksight.VpcConnectionProperties { + if len(tfList) == 0 || tfList[0] == nil { + return nil + } + + tfMap, ok := tfList[0].(map[string]interface{}) + + if !ok { + return nil + } + + props := &quicksight.VpcConnectionProperties{} + + if v, ok := tfMap["vpc_connection_arn"].(string); ok && v != "" { + props.VpcConnectionArn = aws.String(v) + } + + return props +} + +func flattenQuickSightParameters(parameters *quicksight.DataSourceParameters) []interface{} { + if parameters == nil { + return []interface{}{} + } + + var params []interface{} + + if parameters.AmazonElasticsearchParameters != nil { + params = append(params, map[string]interface{}{ + "amazon_elasticsearch": []interface{}{ + map[string]interface{}{ + "domain": parameters.AmazonElasticsearchParameters.Domain, + }, + }, + }) + } + + if parameters.AthenaParameters != nil { + params = append(params, map[string]interface{}{ + "athena": []interface{}{ + map[string]interface{}{ + "work_group": parameters.AthenaParameters.WorkGroup, + }, + }, + }) + } + + if parameters.AuroraParameters != nil { + params = append(params, map[string]interface{}{ + "aurora": []interface{}{ + map[string]interface{}{ + "database": parameters.AuroraParameters.Database, + "host": parameters.AuroraParameters.Host, + "port": parameters.AuroraParameters.Port, + }, + }, + }) + } + + if parameters.AuroraPostgreSqlParameters != nil { + params = append(params, map[string]interface{}{ + "aurora_postgresql": []interface{}{ + map[string]interface{}{ + "database": parameters.AuroraPostgreSqlParameters.Database, + "host": parameters.AuroraPostgreSqlParameters.Host, + "port": parameters.AuroraPostgreSqlParameters.Port, + }, + }, + }) + } + + if parameters.AwsIotAnalyticsParameters != nil { + params = append(params, map[string]interface{}{ + "aws_iot_analytics": []interface{}{ + map[string]interface{}{ + "data_set_name": parameters.AwsIotAnalyticsParameters.DataSetName, + }, + }, + }) + } + + if parameters.JiraParameters != nil { + params = append(params, map[string]interface{}{ + "jira": []interface{}{ + map[string]interface{}{ + "site_base_url": parameters.JiraParameters.SiteBaseUrl, + }, + }, + }) + } + + if parameters.MariaDbParameters != nil { + params = append(params, map[string]interface{}{ + "maria_db": []interface{}{ + map[string]interface{}{ + "database": parameters.MariaDbParameters.Database, + "host": parameters.MariaDbParameters.Host, + "port": parameters.MariaDbParameters.Port, + }, + }, + }) + } + + if parameters.MySqlParameters != nil { + params = append(params, map[string]interface{}{ + "mysql": []interface{}{ + map[string]interface{}{ + "database": parameters.MySqlParameters.Database, + "host": parameters.MySqlParameters.Host, + "port": parameters.MySqlParameters.Port, + }, + }, + }) + } + + if parameters.OracleParameters != nil { + params = append(params, map[string]interface{}{ + "oracle": []interface{}{ + map[string]interface{}{ + "database": parameters.OracleParameters.Database, + "host": parameters.OracleParameters.Host, + "port": parameters.OracleParameters.Port, + }, + }, + }) + } + + if parameters.PostgreSqlParameters != nil { + params = append(params, map[string]interface{}{ + "postgresql": []interface{}{ + map[string]interface{}{ + "database": parameters.PostgreSqlParameters.Database, + "host": parameters.PostgreSqlParameters.Host, + "port": parameters.PostgreSqlParameters.Port, + }, + }, + }) + } + + if parameters.PrestoParameters != nil { + params = append(params, map[string]interface{}{ + "presto": []interface{}{ + map[string]interface{}{ + "catalog": parameters.PrestoParameters.Catalog, + "host": parameters.PrestoParameters.Host, + "port": parameters.PrestoParameters.Port, + }, + }, + }) + } + + if parameters.RdsParameters != nil { + params = append(params, map[string]interface{}{ + "rds": []interface{}{ + map[string]interface{}{ + "database": parameters.RdsParameters.Database, + "instance_id": parameters.RdsParameters.InstanceId, + }, + }, + }) + } + + if parameters.RedshiftParameters != nil { + params = append(params, map[string]interface{}{ + "redshift": []interface{}{ + map[string]interface{}{ + "cluster_id": parameters.RedshiftParameters.ClusterId, + "database": parameters.RedshiftParameters.Database, + "host": parameters.RedshiftParameters.Host, + "port": parameters.RedshiftParameters.Port, + }, + }, + }) + } + + if parameters.S3Parameters != nil { + params = append(params, map[string]interface{}{ + "s3": []interface{}{ + map[string]interface{}{ + "manifest_file_location": []interface{}{ + map[string]interface{}{ + "bucket": parameters.S3Parameters.ManifestFileLocation.Bucket, + "key": parameters.S3Parameters.ManifestFileLocation.Key, + }, + }, + }, + }, + }) + } + + if parameters.ServiceNowParameters != nil { + params = append(params, map[string]interface{}{ + "service_now": []interface{}{ + map[string]interface{}{ + "site_base_url": parameters.ServiceNowParameters.SiteBaseUrl, + }, + }, + }) + } + + if parameters.SnowflakeParameters != nil { + params = append(params, map[string]interface{}{ + "snowflake": []interface{}{ + map[string]interface{}{ + "database": parameters.SnowflakeParameters.Database, + "host": parameters.SnowflakeParameters.Host, + "warehouse": parameters.SnowflakeParameters.Warehouse, + }, + }, + }) + } + + if parameters.SparkParameters != nil { + params = append(params, map[string]interface{}{ + "spark": []interface{}{ + map[string]interface{}{ + "host": parameters.SparkParameters.Host, + "port": parameters.SparkParameters.Port, + }, + }, + }) + } + + if parameters.SqlServerParameters != nil { + params = append(params, map[string]interface{}{ + "sql_server": []interface{}{ + map[string]interface{}{ + "database": parameters.SqlServerParameters.Database, + "host": parameters.SqlServerParameters.Host, + "port": parameters.SqlServerParameters.Port, + }, + }, + }) + } + + if parameters.TeradataParameters != nil { + params = append(params, map[string]interface{}{ + "teradata": []interface{}{ + map[string]interface{}{ + "database": parameters.TeradataParameters.Database, + "host": parameters.TeradataParameters.Host, + "port": parameters.TeradataParameters.Port, + }, + }, + }) + } + + if parameters.TwitterParameters != nil { + params = append(params, map[string]interface{}{ + "twitter": []interface{}{ + map[string]interface{}{ + "max_rows": parameters.TwitterParameters.MaxRows, + "query": parameters.TwitterParameters.Query, + }, + }, + }) + } + + return params +} + +func flattenQuickSightPermissions(perms []*quicksight.ResourcePermission) []interface{} { + if len(perms) == 0 { + return []interface{}{} + } + + values := make([]interface{}, 0) + + for _, p := range perms { + if p == nil { + continue } + + perm := make(map[string]interface{}) + + if p.Principal != nil { + perm["principal"] = aws.StringValue(p.Principal) + } + + if p.Actions != nil { + perm["actions"] = flattenStringList(p.Actions) + } + + values = append(values, perm) } - return nil + return values +} + +func flattenQuickSightSslProperties(props *quicksight.SslProperties) []interface{} { + if props == nil { + return []interface{}{} + } + + m := map[string]interface{}{} + + if props.DisableSsl != nil { + m["disable_ssl"] = aws.BoolValue(props.DisableSsl) + } + + return []interface{}{m} +} + +func flattenQuickSightVpcConnectionProperties(props *quicksight.VpcConnectionProperties) []interface{} { + if props == nil { + return []interface{}{} + } + + m := map[string]interface{}{} + + if props.VpcConnectionArn != nil { + m["vpc_connection_arn"] = aws.StringValue(props.VpcConnectionArn) + } + + return []interface{}{m} } func resourceAwsQuickSightDataSourceParseID(id string) (string, string, error) { parts := strings.SplitN(id, "/", 2) - if len(parts) < 2 || parts[0] == "" || parts[1] == "" { + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { return "", "", fmt.Errorf("unexpected format of ID (%s), expected AWS_ACCOUNT_ID/DATA_SOURCE_ID", id) } return parts[0], parts[1], nil } - -func quicksightDataSourceArn(awsRegion string, awsAccountId string, dataSourceId string) string { - return fmt.Sprintf("arn:aws:quicksight:%s:%s:datasource/%s", awsRegion, awsAccountId, dataSourceId) -} diff --git a/aws/resource_aws_quicksight_data_source_test.go b/aws/resource_aws_quicksight_data_source_test.go index 2cc9cafd7f95..92db8f852e65 100644 --- a/aws/resource_aws_quicksight_data_source_test.go +++ b/aws/resource_aws_quicksight_data_source_test.go @@ -1,29 +1,302 @@ package aws import ( + "context" "fmt" + "log" + "reflect" + "regexp" "testing" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/quicksight" + "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/arn" - "github.com/aws/aws-sdk-go/service/quicksight" ) +func init() { + resource.AddTestSweepers("aws_quicksight_data_source", &resource.Sweeper{ + Name: "aws_quicksight_data_source", + F: testSweepQuickSightDataSources, + }) +} + +func testSweepQuickSightDataSources(region string) error { + client, err := sharedClientForRegion(region) + + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + + conn := client.(*AWSClient).quicksightconn + sweepResources := make([]*testSweepResource, 0) + var errs *multierror.Error + + ctx := context.Background() + awsAccountId := client.(*AWSClient).accountid + + input := &quicksight.ListDataSourcesInput{ + AwsAccountId: aws.String(awsAccountId), + } + + err = conn.ListDataSourcesPagesWithContext(ctx, input, func(page *quicksight.ListDataSourcesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, ds := range page.DataSources { + if ds == nil { + continue + } + + r := resourceAwsQuickSightDataSource() + + d := r.Data(nil) + + d.SetId(fmt.Sprintf("%s/%s", awsAccountId, aws.StringValue(ds.DataSourceId))) + + sweepResources = append(sweepResources, NewTestSweepResource(r, d, client)) + } + + return !lastPage + }) + + if err != nil { + errs = multierror.Append(errs, fmt.Errorf("error describing QuickSigth Data Sources: %w", err)) + } + + if err := testSweepResourceOrchestrator(sweepResources); err != nil { + errs = multierror.Append(errs, fmt.Errorf("error sweeping QuickSight Data Sources for %s: %w", region, err)) + } + + if testSweepSkipSweepError(errs.ErrorOrNil()) { + log.Printf("[WARN] Skipping QuickSight Data Source sweep for %s: %s", region, errs) + return nil + } + + return errs.ErrorOrNil() +} + +func TestQuickSightDataSourcePermissionsDiff(t *testing.T) { + testCases := []struct { + name string + oldPermissions []interface{} + newPermissions []interface{} + expectedGrants []*quicksight.ResourcePermission + expectedRevokes []*quicksight.ResourcePermission + }{ + { + name: "no changes;empty", + oldPermissions: []interface{}{}, + newPermissions: []interface{}{}, + expectedGrants: nil, + expectedRevokes: nil, + }, + { + name: "no changes;same", + oldPermissions: []interface{}{ + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action1", + "action2", + }), + }, + }, + newPermissions: []interface{}{ + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action1", + "action2", + }), + }}, + + expectedGrants: nil, + expectedRevokes: nil, + }, + { + name: "grant only", + oldPermissions: []interface{}{}, + newPermissions: []interface{}{ + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action1", + "action2", + }), + }, + }, + expectedGrants: []*quicksight.ResourcePermission{ + { + Actions: aws.StringSlice([]string{"action1", "action2"}), + Principal: aws.String("principal1"), + }, + }, + expectedRevokes: nil, + }, + { + name: "revoke only", + oldPermissions: []interface{}{ + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action1", + "action2", + }), + }, + }, + newPermissions: []interface{}{}, + expectedGrants: nil, + expectedRevokes: []*quicksight.ResourcePermission{ + { + Actions: aws.StringSlice([]string{"action1", "action2"}), + Principal: aws.String("principal1"), + }, + }, + }, + { + name: "grant new action", + oldPermissions: []interface{}{ + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action1", + }), + }, + }, + newPermissions: []interface{}{ + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action1", + "action2", + }), + }, + }, + expectedGrants: []*quicksight.ResourcePermission{ + { + Actions: aws.StringSlice([]string{"action2"}), + Principal: aws.String("principal1"), + }, + }, + expectedRevokes: nil, + }, + { + name: "revoke old action", + oldPermissions: []interface{}{ + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "oldAction", + "onlyOldAction", + }), + }, + }, + newPermissions: []interface{}{ + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "oldAction", + }), + }, + }, + expectedGrants: nil, + expectedRevokes: []*quicksight.ResourcePermission{ + { + Actions: aws.StringSlice([]string{"onlyOldAction"}), + Principal: aws.String("principal1"), + }, + }, + }, + { + name: "multiple permissions", + oldPermissions: []interface{}{ + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action1", + "action2", + }), + }, + map[string]interface{}{ + "principal": "principal2", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action1", + "action3", + "action4", + }), + }, + map[string]interface{}{ + "principal": "principal3", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action5", + }), + }, + }, + newPermissions: []interface{}{ + map[string]interface{}{ + "principal": "principal1", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action1", + "action2", + }), + }, + map[string]interface{}{ + "principal": "principal2", + "actions": schema.NewSet(schema.HashString, []interface{}{ + "action3", + "action5", + }), + }, + }, + expectedGrants: []*quicksight.ResourcePermission{ + { + Actions: aws.StringSlice([]string{"action5"}), + Principal: aws.String("principal2"), + }, + }, + expectedRevokes: []*quicksight.ResourcePermission{ + { + Actions: aws.StringSlice([]string{"action1", "action4"}), + Principal: aws.String("principal2"), + }, + { + Actions: aws.StringSlice([]string{"action5"}), + Principal: aws.String("principal3"), + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + toGrant, toRevoke := diffQuickSightDataSourcePermissions(testCase.oldPermissions, testCase.newPermissions) + if !reflect.DeepEqual(toGrant, testCase.expectedGrants) { + t.Fatalf("Expected: %v, got: %v", testCase.expectedGrants, toGrant) + } + + if !reflect.DeepEqual(toRevoke, testCase.expectedRevokes) { + t.Fatalf("Expected: %v, got: %v", testCase.expectedRevokes, toRevoke) + } + }) + } +} + func TestAccAWSQuickSightDataSource_basic(t *testing.T) { var dataSource quicksight.DataSource - resourceName := "aws_quicksight_data_source.default" + resourceName := "aws_quicksight_data_source.test" rName := acctest.RandomWithPrefix("tf-acc-test") rId := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - ErrorCheck: testAccErrorCheck(t, quicksight.EndpointsID), - CheckDestroy: testAccCheckQuickSightDataSourceDestroy, + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + ErrorCheck: testAccErrorCheck(t, quicksight.EndpointsID), + CheckDestroy: testAccCheckQuickSightDataSourceDestroy, Steps: []resource.TestStep{ { Config: testAccAWSQuickSightDataSourceConfig(rId, rName), @@ -31,6 +304,14 @@ func TestAccAWSQuickSightDataSource_basic(t *testing.T) { testAccCheckQuickSightDataSourceExists(resourceName, &dataSource), resource.TestCheckResourceAttr(resourceName, "data_source_id", rId), testAccCheckResourceAttrRegionalARN(resourceName, "arn", "quicksight", fmt.Sprintf("datasource/%s", rId)), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "parameters.#", "1"), + 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.TestCheckResourceAttr(resourceName, "type", quicksight.DataSourceTypeS3), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, { @@ -44,21 +325,21 @@ func TestAccAWSQuickSightDataSource_basic(t *testing.T) { func TestAccAWSQuickSightDataSource_disappears(t *testing.T) { var dataSource quicksight.DataSource - resourceName := "aws_quicksight_data_source.default" + resourceName := "aws_quicksight_data_source.test" rName := acctest.RandomWithPrefix("tf-acc-test") rId := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - ErrorCheck: testAccErrorCheck(t, quicksight.EndpointsID), - CheckDestroy: testAccCheckQuickSightDataSourceDestroy, + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + ErrorCheck: testAccErrorCheck(t, quicksight.EndpointsID), + CheckDestroy: testAccCheckQuickSightDataSourceDestroy, Steps: []resource.TestStep{ { Config: testAccAWSQuickSightDataSourceConfig(rId, rName), Check: resource.ComposeTestCheckFunc( testAccCheckQuickSightDataSourceExists(resourceName, &dataSource), - testAccCheckQuickSightDataSourceDisappears(&dataSource), + testAccCheckResourceDisappears(testAccProvider, resourceAwsQuickSightDataSource(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -66,6 +347,114 @@ func TestAccAWSQuickSightDataSource_disappears(t *testing.T) { }) } +func TestAccAWSQuickSightDataSource_Tags(t *testing.T) { + var dataSource quicksight.DataSource + resourceName := "aws_quicksight_data_source.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + rId := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, quicksight.EndpointsID), + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckQuickSightDataSourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSQuickSightDataSourceConfigTags1(rId, rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckQuickSightDataSourceExists(resourceName, &dataSource), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSQuickSightDataSourceConfigTags2(rId, rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckQuickSightDataSourceExists(resourceName, &dataSource), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAWSQuickSightDataSourceConfigTags1(rId, rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckQuickSightDataSourceExists(resourceName, &dataSource), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + }, + }) +} + +func TestAccAWSQuickSightDataSource_Permissions(t *testing.T) { + var dataSource quicksight.DataSource + resourceName := "aws_quicksight_data_source.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + rId := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories, + ErrorCheck: testAccErrorCheck(t, quicksight.EndpointsID), + CheckDestroy: testAccCheckQuickSightDataSourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSQuickSightDataSourceConfig_Permissions(rId, rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckQuickSightDataSourceExists(resourceName, &dataSource), + resource.TestCheckResourceAttr(resourceName, "permission.#", "1"), + resource.TestMatchTypeSetElemNestedAttrs(resourceName, "permission.*", map[string]*regexp.Regexp{ + "principal": regexp.MustCompile(fmt.Sprintf(`user/default/%s`, rName)), + }), + resource.TestCheckTypeSetElemAttr(resourceName, "permission.*.actions.*", "quicksight:DescribeDataSource"), + resource.TestCheckTypeSetElemAttr(resourceName, "permission.*.actions.*", "quicksight:DescribeDataSourcePermissions"), + resource.TestCheckTypeSetElemAttr(resourceName, "permission.*.actions.*", "quicksight:PassDataSource"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSQuickSightDataSourceConfig_UpdatePermissions(rId, rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckQuickSightDataSourceExists(resourceName, &dataSource), + resource.TestCheckResourceAttr(resourceName, "permission.#", "1"), + resource.TestMatchTypeSetElemNestedAttrs(resourceName, "permission.*", map[string]*regexp.Regexp{ + "principal": regexp.MustCompile(fmt.Sprintf(`user/default/%s`, rName)), + }), + resource.TestCheckTypeSetElemAttr(resourceName, "permission.*.actions.*", "quicksight:DescribeDataSource"), + resource.TestCheckTypeSetElemAttr(resourceName, "permission.*.actions.*", "quicksight:DescribeDataSourcePermissions"), + resource.TestCheckTypeSetElemAttr(resourceName, "permission.*.actions.*", "quicksight:PassDataSource"), + resource.TestCheckTypeSetElemAttr(resourceName, "permission.*.actions.*", "quicksight:UpdateDataSource"), + resource.TestCheckTypeSetElemAttr(resourceName, "permission.*.actions.*", "quicksight:DeleteDataSource"), + resource.TestCheckTypeSetElemAttr(resourceName, "permission.*.actions.*", "quicksight:UpdateDataSourcePermissions"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSQuickSightDataSourceConfig(rId, rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckQuickSightDataSourceExists(resourceName, &dataSource), + resource.TestCheckResourceAttr(resourceName, "permission.#", "0"), + ), + }, + }, + }) +} + func testAccCheckQuickSightDataSourceExists(resourceName string, dataSource *quicksight.DataSource) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceName] @@ -113,10 +502,11 @@ func testAccCheckQuickSightDataSourceDestroy(s *terraform.State) error { return err } - _, err = conn.DescribeDataSource(&quicksight.DescribeDataSourceInput{ + output, err := conn.DescribeDataSource(&quicksight.DescribeDataSourceInput{ AwsAccountId: aws.String(awsAccountID), DataSourceId: aws.String(dataSourceId), }) + if isAWSErr(err, quicksight.ErrCodeResourceNotFoundException, "") { continue } @@ -125,71 +515,203 @@ func testAccCheckQuickSightDataSourceDestroy(s *terraform.State) error { return err } - return fmt.Errorf("QuickSight Data Source '%s' was not deleted properly", rs.Primary.ID) + if output != nil && output.DataSource != nil { + return fmt.Errorf("QuickSight Data Source (%s) still exists", rs.Primary.ID) + } } return nil } -func testAccCheckQuickSightDataSourceDisappears(v *quicksight.DataSource) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).quicksightconn +func testAccAWSQuickSightDataSourceConfigBase(rName string) string { + return fmt.Sprintf(` +data "aws_partition" "current" {} - arn, err := arn.Parse(aws.StringValue(v.Arn)) - if err != nil { - return err - } +resource "aws_s3_bucket" "test" { + acl = "public-read" + bucket = %[1]q + force_destroy = true +} - input := &quicksight.DeleteDataSourceInput{ - AwsAccountId: aws.String(arn.AccountID), - DataSourceId: v.DataSourceId, - } +resource "aws_s3_bucket_object" "test" { + bucket = aws_s3_bucket.test.bucket + key = %[1]q + content = < 0 { - toGrant = append(toGrant, grant) - } - - if len(revoke.Actions) > 0 { - toRevoke = append(toRevoke, revoke) - } - } - - return toGrant, toRevoke -} - -func diffQuickSightPermissionsLookup(oldPerms []interface{}, newPerms []interface{}) map[string]map[string]bool { - // Map principal to permissions. `true` means grant, `false` means - // revoke and absence means leave alone (i.e. unchanged) - grants := make(map[string]map[string]bool) - - // All new params should be granted until further notice... - for _, v := range newPerms { - s := v.(map[string]interface{}) - - if p, ok := s["principal"].(string); ok { - if _, present := grants[p]; !present { - grants[p] = make(map[string]bool) - } - - for _, v := range s["actions"].(*schema.Set).List() { - grants[p][v.(string)] = true - } - } - } - - // Don't touch principal-action combos that are unchanged, revoke combos that - // are in old but not new - for _, v := range oldPerms { - s := v.(map[string]interface{}) - - if p, ok := s["principal"].(string); ok { - if principalGrants, present := grants[p]; present { - for _, v := range s["actions"].(*schema.Set).List() { - action := v.(string) - - if _, present := principalGrants[action]; !present { - // In old but not in new so revoke - grants[p][action] = false - } else { - // In old and in new so leave alone - delete(grants[p], action) - } - } - } else { - grants[p] = make(map[string]bool) - - // The principal is not desired in new - // permissions so revoke all - for _, v := range s["actions"].(*schema.Set).List() { - grants[p][v.(string)] = false - } - } - - } - } - - return grants -} - -func flattenQuickSightPermissions(perms []*quicksight.ResourcePermission) []map[string]interface{} { - values := make([]map[string]interface{}, 0) - - for _, v := range perms { - perm := make(map[string]interface{}) - - if v == nil { - return nil - } - - if v.Principal != nil { - perm["principal"] = *v.Principal - } - - if v.Actions != nil { - perm["actions"] = flattenStringList(v.Actions) - } - - values = append(values, perm) - } - - return values -} - func flattenRedshiftLogging(ls *redshift.LoggingStatus) []interface{} { if ls == nil { return []interface{}{} diff --git a/aws/structure_test.go b/aws/structure_test.go index 190b79f097f3..199aa17954e1 100644 --- a/aws/structure_test.go +++ b/aws/structure_test.go @@ -1403,229 +1403,6 @@ func TestCognitoUserPoolSchemaAttributeMatchesStandardAttribute(t *testing.T) { } } -func TestDiffQuickSightNoPermissionsMeansNoChanges(t *testing.T) { - empty := make([]interface{}, 0) - - toGrant, toRevoke := diffQuickSightPermissionsToGrantAndRevoke(empty, empty) - - if len(toGrant) > 0 { - t.Fatal("Expected no granted permissions, got", len(toGrant)) - } - - if len(toRevoke) > 0 { - t.Fatal("Expected no revoked permissions, got", len(toRevoke)) - } -} - -func TestDiffQuickSightNoOldMeansOnlyGrant(t *testing.T) { - empty := make([]interface{}, 0) - newPerms := []interface{}{ - map[string]interface{}{ - "principal": "principal1", - "actions": schema.NewSet(schema.HashString, []interface{}{ - "action1", - "action2", - }), - }, - } - - toGrant, toRevoke := diffQuickSightPermissionsToGrantAndRevoke(empty, newPerms) - - if len(toGrant) != 1 { - t.Fatal("Expected 1 granted permission, got", len(toGrant)) - } - - if len(toRevoke) > 0 { - t.Fatal("Expected no revoked permissions, got", len(toRevoke)) - } -} - -func TestDiffQuickSightNoNewMeansOnlyRevoke(t *testing.T) { - empty := make([]interface{}, 0) - oldPerms := []interface{}{ - map[string]interface{}{ - "principal": "principal1", - "actions": schema.NewSet(schema.HashString, []interface{}{ - "action1", - "action2", - }), - }, - } - - toGrant, toRevoke := diffQuickSightPermissionsToGrantAndRevoke(oldPerms, empty) - - if len(toGrant) > 0 { - t.Fatal("Expected no granted permissions, got", len(toGrant)) - } - - if len(toRevoke) != 1 { - t.Fatal("Expected 1 revoked permission, got", len(toRevoke)) - } -} - -func TestDiffQuickSightIntersectingPermissionsMeansNoChange(t *testing.T) { - perms := []interface{}{ - map[string]interface{}{ - "principal": "principal1", - "actions": schema.NewSet(schema.HashString, []interface{}{ - "action1", - "action2", - }), - }, - } - - toGrant, toRevoke := diffQuickSightPermissionsToGrantAndRevoke(perms, perms) - - if len(toGrant) > 0 { - t.Fatal("Expected no granted permissions, got", len(toGrant)) - } - - if len(toRevoke) > 0 { - t.Fatal("Expected no revoked permissions, got", len(toRevoke)) - } -} - -func TestDiffQuickSightAdditionalNewPermissionsBecomeGrants(t *testing.T) { - oldPerms := []interface{}{ - map[string]interface{}{ - "principal": "principal1", - "actions": schema.NewSet(schema.HashString, []interface{}{ - "oldAction", - }), - }, - } - newPerms := []interface{}{ - map[string]interface{}{ - "principal": "principal1", - "actions": schema.NewSet(schema.HashString, []interface{}{ - "oldAction", - "newAction", - }), - }, - } - - toGrant, toRevoke := diffQuickSightPermissionsToGrantAndRevoke(oldPerms, newPerms) - - if len(toGrant) != 1 { - t.Fatal("Expected 1 granted permission, got", len(toGrant)) - } - - if len(toGrant[0].Actions) != 1 || *toGrant[0].Actions[0] != "newAction" { - t.Fatal("Expected 1 new granted action, got", len(toGrant)) - } - - if len(toRevoke) > 0 { - t.Fatal("Expected no revoked permissions, got", len(toRevoke)) - } -} - -func TestDiffQuickSightAdditionalOldPermissionsBecomeRevokes(t *testing.T) { - oldPerms := []interface{}{ - map[string]interface{}{ - "principal": "principal1", - "actions": schema.NewSet(schema.HashString, []interface{}{ - "oldAction", - "onlyOldAction", - }), - }, - } - newPerms := []interface{}{ - map[string]interface{}{ - "principal": "principal1", - "actions": schema.NewSet(schema.HashString, []interface{}{ - "oldAction", - }), - }, - } - - toGrant, toRevoke := diffQuickSightPermissionsToGrantAndRevoke(oldPerms, newPerms) - - if len(toRevoke) != 1 { - t.Fatal("Expected 1 revoked permission, got", len(toRevoke)) - } - - if len(toRevoke[0].Actions) != 1 || *toRevoke[0].Actions[0] != "onlyOldAction" { - t.Fatal("Expected 1 revoked action, got", len(toRevoke)) - } - - if len(toGrant) > 0 { - t.Fatal("Expected no granted permissions, got", len(toGrant)) - } -} - -func TestDiffQuickSightFatTest(t *testing.T) { - oldPerms := []interface{}{ - map[string]interface{}{ - "principal": "principal1", - "actions": schema.NewSet(schema.HashString, []interface{}{ - "action1", - }), - }, - map[string]interface{}{ - "principal": "principal2", - "actions": schema.NewSet(schema.HashString, []interface{}{ - "action1", - "action3", - "action4", - }), - }, - map[string]interface{}{ - "principal": "principal1", - "actions": schema.NewSet(schema.HashString, []interface{}{ - "action2", - }), - }, - map[string]interface{}{ - "principal": "principal3", - "actions": schema.NewSet(schema.HashString, []interface{}{ - "action5", - }), - }, - } - newPerms := []interface{}{ - // Leave action3 untouched, grant action5 and revoke action1 - // and action4 - map[string]interface{}{ - "principal": "principal2", - "actions": schema.NewSet(schema.HashString, []interface{}{ - "action3", - "action5", - }), - }, - // Should span principals, leaving all actions untouched for - // principal1 - map[string]interface{}{ - "principal": "principal1", - "actions": schema.NewSet(schema.HashString, []interface{}{ - "action1", - "action2", - }), - }, - // Don't mention principal3 which means revoke - } - - expected := map[string]map[string]bool{ - "principal2": { - "action1": false, - "action4": false, - "action5": true, - }, - "principal3": { - "action5": false, - }, - "principal1": make(map[string]bool), - } - - lookup := diffQuickSightPermissionsLookup(oldPerms, newPerms) - - if !reflect.DeepEqual(lookup, expected) { - t.Fatalf( - "Got:\n\n%#v\n\nExpected:\n\n%#v\n", - lookup, - expected) - } -} - func TestCanonicalXML(t *testing.T) { cases := []struct { Name string diff --git a/aws/tagsQuickSight.go b/aws/tagsQuickSight.go deleted file mode 100644 index cd9c87f0ee54..000000000000 --- a/aws/tagsQuickSight.go +++ /dev/null @@ -1,46 +0,0 @@ -package aws - -import ( - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/quicksight" -) - -func diffTagsQuickSight(oldTags, newTags []*quicksight.Tag) ([]*quicksight.Tag, []*quicksight.Tag) { - create := make(map[string]interface{}) - for _, t := range newTags { - create[aws.StringValue(t.Key)] = aws.StringValue(t.Value) - } - - var remove []*quicksight.Tag - for _, t := range oldTags { - old, ok := create[aws.StringValue(t.Key)] - if !ok || old != aws.StringValue(t.Value) { - remove = append(remove, t) - } else if ok { - delete(create, aws.StringValue(t.Key)) - } - } - - return tagsFromMapQuickSight(create), remove -} - -func tagsFromMapQuickSight(m map[string]interface{}) []*quicksight.Tag { - result := make([]*quicksight.Tag, 0, len(m)) - for k, v := range m { - t := &quicksight.Tag{ - Key: aws.String(k), - Value: aws.String(v.(string)), - } - result = append(result, t) - } - - return result -} - -func tagKeysQuickSight(ts []*quicksight.Tag) []*string { - result := make([]*string, 0, len(ts)) - for _, t := range ts { - result = append(result, t.Key) - } - return result -} diff --git a/website/docs/r/quicksight_data_source.html.markdown b/website/docs/r/quicksight_data_source.html.markdown index 44f4807cfec7..585a33082666 100644 --- a/website/docs/r/quicksight_data_source.html.markdown +++ b/website/docs/r/quicksight_data_source.html.markdown @@ -14,199 +14,198 @@ Resource for managing QuickSight Data Source ```terraform resource "aws_quicksight_data_source" "default" { - data_source_id = "abcdefg" + data_source_id = "example-id" name = "My Cool Data in S3" + parameters { s3 { manifest_file_location { - bucket = "my.bucket" + bucket = "my-bucket" key = "path/to/manifest.json" } } } + + type = "S3" } ``` ## Argument Reference -The QuickSight data source argument layout is a complex structure composed -of several sub-resources - these resources are laid out below. - -### Top-Level Arguments - -* `name` - (Required) A name for the data source. - -* `data_source_id` - (Required) An identifier for the data source. - -* `aws_account_id` - (Optional) The ID for the AWS account that the data source is in. Currently, you use the ID for the AWS account that contains your Amazon QuickSight account. - -* `parameters` - (Required) The [parameters](#parameters-arguments) used to connect to this data source (exactly one). - -* `credentials` - (Optional) The credentials Amazon QuickSight that uses to connect to your underlying source. Currently, only credentials based on user name and password are supported. - -#### Parameters Arguments - -To specify data source connection parameters, exactly one of the following sub-objects must be provided. +The following arguments are required: -* `amazon_elasticsearch` - [Parameters](#amazon-elasticsearch-arguments) for connecting to Amazon Elasticsearch. +* `data_source_id` - (Required, Forces new resource) An identifier for the data source. +* `name` - (Required) A name for the data source, maximum of 128 characters. +* `parameters` - (Required) The [parameters](#parameters-argument-reference) used to connect to this data source (exactly one). +* `type` - (Required) The type of the data source. See the [AWS Documentation](https://docs.aws.amazon.com/quicksight/latest/APIReference/API_CreateDataSource.html#QS-CreateDataSource-request-Type) for the complete list of valid values. -* `athena` - [Parameters](#athena-arguments) for connecting to Athena. +The following arguments are optional: -* `aurora` - [Parameters](#aurora-arguments) for connecting to Athena. +* `aws_account_id` - (Optional, Forces new resource) The ID for the AWS account that the data source is in. Currently, you use the ID for the AWS account that contains your Amazon QuickSight account. +* `credentials` - (Optional) The credentials Amazon QuickSight uses to connect to your underlying source. Currently, only credentials based on user name and password are supported. See [Credentials](#credentials-argument-reference) below for more details. +* `permission` - (Optional) A set of resource permissions on the data source. Maximum of 64 items. See [Permission](#permission-argument-reference) below for more details. +* `ssl_properties` - (Optional) Secure Socket Layer (SSL) properties that apply when Amazon QuickSight connects to your underlying source. See [SSL Properties](#ssl_properties-argument-reference) below for more details. +* `tags` - (Optional) Key-value map of resource tags. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. +* `vpc_connection_properties`- (Optional) Use this parameter only when you want Amazon QuickSight to use a VPC connection when connecting to your underlying source. See [VPC Connection Properties](#vpc_connection_properties-argument-reference) below for more details. -* `aurora_postgresql` - [Parameters](#aurora-postgresql-arguments) for connecting to Aurora Postgresql. +### credentials Argument Reference -* `aws_iot_analytics` - [Parameters](#aws-iot-analytics-arguments) for connecting to AWS IOT Analytics. +* `copy_source_arn` (Optional, Conflicts with `credential_pair`) - The Amazon Resource Name (ARN) of a data source that has the credential pair that you want to use. +When the value is not null, the `credential_pair` from the data source in the ARN is used. +* `credential_pair` (Optional, Conflicts with `copy_source_arn`) - Credential pair. See [Credential Pair](#credential_pair-argument-reference) below for more details. -* `jira` - [Parameters](#jira-arguments) for connecting to Jira. +### credential_pair Argument Reference -* `maria_db` - [Parameters](#mariadb-arguments) for connecting to MariaDB. +* `password` - (Required) Password, maximum length of 1024 characters. +* `username` - (Required) User name, maximum length of 64 characters. -* `mysql` - [Parameters](#mysql-arguments) for connecting to MySQL. +### parameters Argument Reference -* `postgresql` - [Parameters](#postgresql-arguments) for connecting to Postgresql. - -* `presto` - [Parameters](#presto-arguments) for connecting to Presto. - -* `redshift` - [Parameters](#redshift-arguments) for connecting to Redshift. +To specify data source connection parameters, exactly one of the following sub-objects must be provided. -* `s3` - [Parameters](#s3-arguments) for connecting to S3. +* `amazon_elasticsearch` - (Optional) [Parameters](#amazon_elasticsearch-argument-reference) for connecting to Amazon Elasticsearch. +* `athena` - (Optional) [Parameters](#athena-argument-reference) for connecting to Athena. +* `aurora` - (Optional) [Parameters](#aurora-argument-reference) for connecting to Aurora MySQL. +* `aurora_postgresql` - (Optional) [Parameters](#aurora_postgresql-argument-reference) for connecting to Aurora Postgresql. +* `aws_iot_analytics` - (Optional) [Parameters](#aws_iot_analytics-argument-reference) for connecting to AWS IOT Analytics. +* `jira` - (Optional) [Parameters](#jira-fargument-reference) for connecting to Jira. +* `maria_db` - (Optional) [Parameters](#maria_db-argument-reference) for connecting to MariaDB. +* `mysql` - (Optional) [Parameters](#mysql-argument-reference) for connecting to MySQL. +* `oracle` - (Optional) [Parameters](#oracle-argument-reference) for connecting to Oracle. +* `postgresql` - (Optional) [Parameters](#postgresql-argument-reference) for connecting to Postgresql. +* `presto` - (Optional) [Parameters](#presto-argument-reference) for connecting to Presto. +* `rds` - (Optional) [Parameters](#rds-argument-reference) for connecting to RDS. +* `redshift` - (Optional) [Parameters](#redshift-argument-reference) for connecting to Redshift. +* `s3` - (Optional) [Parameters](#s3-argument-reference) for connecting to S3. +* `service_now` - (Optional) [Parameters](#service_now-argument-reference) for connecting to ServiceNow. +* `snowflake` - (Optional) [Parameters](#snowflake-argument-reference) for connecting to Snowflake. +* `spark` - (Optional) [Parameters](#spark-argument-reference) for connecting to Spark. +* `sql_server` - (Optional) [Parameters](#sql_server-argument-reference) for connecting to SQL Server. +* `teradata` - (Optional) [Parameters](#teradata-argument-reference) for connecting to Teradata. +* `twitter` - (Optional) [Parameters](#twitter-argument-reference) for connecting to Twitter. -* `service_now` - [Parameters](#servicenow-arguments) for connecting to ServiceNow. +### permission Argument Reference -* `snowflake` - [Parameters](#snowflake-arguments) for connecting to Snowflake. +* `actions` - (Required) Set of IAM actions to grant or revoke permissions on. Max of 16 items. +* `principal` - (Required) The Amazon Resource Name (ARN) of the principal. -* `spark` - [Parameters](#spark-arguments) for connecting to SPARK. +### ssl_properties Argument Reference -* `sql_server` - [Parameters](#sqlserver-arguments) for connecting to SqlServer. +* `disable_ssl` - (Required) A Boolean option to control whether SSL should be disabled. -* `teradata` - [Parameters](#teradata-arguments) for connecting to Teradata. +### vpc_connection_properties Argument Reference -* `twitter` - [Parameters](#twitter-arguments) for connecting to Twitter. +* `vpc_connection_arn` - (Required) The Amazon Resource Name (ARN) for the VPC connection. -#### Amazon Elasticsearch Arguments +### amazon_elasticsearch Argument Reference -* `domain` - (Required) The domain to which to connect. +* `domain` - (Required) The OpenSearch domain. -#### Athena Arguments +### athena Argument Reference * `work_group` - (Optional) The work-group to which to connect. -#### Aurora Arguments +### aurora Argument Reference * `database` - (Required) The database to which to connect. - * `host` - (Required) The host to which to connect. - * `port` - (Required) The port to which to connect. -#### Aurora Postgresql Arguments +### aurora_postgresql Argument Reference * `database` - (Required) The database to which to connect. - * `host` - (Required) The host to which to connect. - * `port` - (Required) The port to which to connect. -#### AWS IOT Analytics Postgresql Arguments +### aws_iot_analytics Argument Reference * `data_set_name` - (Required) The name of the data set to which to connect. -#### Jira Arguments +### jira fArgument Reference * `site_base_url` - (Required) The base URL of the Jira instance's site to which to connect. -#### MariaDB Arguments +### maria_db Argument Reference * `database` - (Required) The database to which to connect. - * `host` - (Required) The host to which to connect. - * `port` - (Required) The port to which to connect. -#### MySQL Arguments +### mysql Argument Reference * `database` - (Required) The database to which to connect. - * `host` - (Required) The host to which to connect. - * `port` - (Required) The port to which to connect. -#### Postgresql Arguments +### oracle Argument Reference * `database` - (Required) The database to which to connect. - * `host` - (Required) The host to which to connect. +* `port` - (Required) The port to which to connect. +### postgresql Argument Reference + +* `database` - (Required) The database to which to connect. +* `host` - (Required) The host to which to connect. * `port` - (Required) The port to which to connect. -#### Presto Arguments +### presto Argument Reference * `catalog` - (Required) The catalog to which to connect. - * `host` - (Required) The host to which to connect. - * `port` - (Required) The port to which to connect. -#### Redshift Arguments - -* `cluster_id` - (Optional) The ID of the cluster to which to connect. +### rds Argument Reference * `database` - (Required) The database to which to connect. +* `instance_id` - (Optional) The instance ID to which to connect. -* `host` - (Optional) The host to which to connect. +### redshift Argument Reference -* `port` - (Optional) The port to which to connect. +* `cluster_id` - (Optional, Required if `host` and `port` are not provided) The ID of the cluster to which to connect. +* `database` - (Required) The database to which to connect. +* `host` - (Optional, Required if `cluster_id` is not provided) The host to which to connect. +* `port` - (Optional, Required if `cluster_id` is not provided) The port to which to connect. -#### S3 Arguments +### s3 Argument Reference -* `manifest_file_location` - (Required) An [object containing the S3 location](#manifest-file-location-arguments) of the S3 manifest file. +* `manifest_file_location` - (Required) An [object containing the S3 location](#manifest_file_location-argument-reference) of the S3 manifest file. -##### Manifest File Location Arguments +### manifest_file_location Argument Reference * `bucket` - (Required) The name of the bucket that contains the manifest file. - * `key` - (Required) The key of the manifest file within the bucket. - -#### ServiceNow Arguments +### service_now Argument Reference * `site_base_url` - (Required) The base URL of the Jira instance's site to which to connect. -#### Snowflake Arguments +### snowflake Argument Reference * `database` - (Required) The database to which to connect. - * `host` - (Required) The host to which to connect. - * `warehouse` - (Required) The warehouse to which to connect. -#### SPARK Arguments +### spark Argument Reference * `host` - (Required) The host to which to connect. - * `port` - (Required) The warehouse to which to connect. -#### SqlServer Arguments +### sql_server Argument Reference * `database` - (Required) The database to which to connect. - * `host` - (Required) The host to which to connect. - * `port` - (Required) The warehouse to which to connect. -#### Teradata Arguments +### teradata Argument Reference * `database` - (Required) The database to which to connect. - * `host` - (Required) The host to which to connect. - * `port` - (Required) The warehouse to which to connect. -#### Twitter Arguments +#### twitter Argument Reference * `max_rows` - (Required) The maximum number of rows to query. - * `query` - (Required) The Twitter query to retrieve the data. ## Attributes Reference @@ -214,12 +213,11 @@ To specify data source connection parameters, exactly one of the following sub-o In addition to all arguments above, the following attributes are exported: * `arn` - Amazon Resource Name (ARN) of the data source - -* `type` - A key indicating which data source type was inferred from the passed `parameters` +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block). ## Import -A QuickSight data source can be imported using the AWS account ID, and data source ID name separated by `/`. +A QuickSight data source can be imported using the AWS account ID, and data source ID name separated by a slash (`/`) e.g. ``` $ terraform import aws_quicksight_data_source.example 123456789123/my-data-source-id