Skip to content

microsoftexpert/tf-mod-aws-quicksight

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🟧 AWS QuickSight Terraform Module

A secure-by-default, aggregation-flavored composite for Amazon QuickSight — data sources, data sets with row/column-level security, analyses, dashboards, templates, themes, folders, groups/users, VPC connections, and refresh schedules — the largest resource count in the AWS gap-closure catalog. Built for the AWS provider v6.x.

Terraform >= 1.12 aws 6.x module v1.0.0 type=composite resources=25


🧩 Overview

  • 📊 Creates and wires QuickSight data sources across ~21 connector types (S3, Athena, Redshift, RDS, Aurora, Snowflake, and more) with deeply-typed, non-any connection parameters.
  • 🗃️ Builds data sets with full physical/logical table transform pipelines, plus row-level and column-level security — the primary controls for restricting who sees which rows/columns of financial or member data.
  • 📈 Promotes analyses → templates → dashboards via the source_entity template-promotion pattern (AWS's own recommended production path).
  • 🗂️ Organizes content into folders, manages groups/users/namespaces, and grants IAM policy assignments and custom-permissions profiles.
  • 🔒 Wires private VPC connectivity for data sources instead of the public internet, and supports customer-managed KMS key registration and account-wide IP allow-listing.
  • ⏱️ Schedules SPICE refreshes (recurring) and one-shot ingestions.

💡 Why it matters: QuickSight dashboards routinely surface member-identifiable financial data at — the difference between a data set with row-level security and one without is the difference between "each loan officer sees their own portfolio" and "every viewer sees every member's balance."


❤️ Support this project

If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:

Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!


🗺️ Where this fits in the family

flowchart LR
 VPC["tf-mod-aws-vpc"] --> VPCCONN["vpc_connections"]
 SG["tf-mod-aws-security-group"] --> VPCCONN
 IAMROLE["tf-mod-aws-iam-role"] --> VPCCONN
 IAMROLE --> DS["data_sources"]
 IAMPOLICY["tf-mod-aws-iam-policy"] --> IPA["iam_policy_assignments"]
 KMS["tf-mod-aws-kms"] --> KEYREG["key_registration"]
 SECRETS["tf-mod-aws-secrets-manager"] --> DS
 RDS["tf-mod-aws-rds / tf-mod-aws-rds-aurora"] -.->|connection params| DS
 REDSHIFT["tf-mod-aws-redshift"] -.->|connection params| DS
 S3["tf-mod-aws-s3-bucket"] -.->|manifest / source| DS

 subgraph QS["tf-mod-aws-quicksight"]
 DS --> DSET["data_sets"]
 VPCCONN --> DS
 DSET --> ANALYSIS["analyses"]
 DSET --> TEMPLATE["templates"]
 ANALYSIS --> TEMPLATE
 TEMPLATE --> DASH["dashboards"]
 DASH --> FOLDER["folders"]
 DSET --> REFRESH["refresh_schedules"]
 end

 style QS fill:#FF9900,color:#fff
Loading

🧬 What this module builds

flowchart TB
 NS["aws_quicksight_namespace"] --> GRP["aws_quicksight_group"]
 NS --> USR["aws_quicksight_user"]
 GRP --> GRPM["aws_quicksight_group_membership"]
 USR --> IPA2["aws_quicksight_iam_policy_assignment"]

 VPCC["aws_quicksight_vpc_connection"] --> DSRC["aws_quicksight_data_source"]
 DSRC --> DSET2["aws_quicksight_data_set"]
 DSET2 --> ING["aws_quicksight_ingestion"]
 DSET2 --> REFR["aws_quicksight_refresh_schedule"]

 DSET2 --> ANL["aws_quicksight_analysis"]
 THEME["aws_quicksight_theme"] --> ANL
 ANL --> TMPL["aws_quicksight_template"]
 TMPL --> TALIAS["aws_quicksight_template_alias"]
 TMPL --> DASH2["aws_quicksight_dashboard"]
 THEME --> DASH2

 FLD["aws_quicksight_folder"] --> FMEM["aws_quicksight_folder_membership"]
 DASH2 --> FMEM
 ANL --> FMEM
 DSET2 --> FMEM

 CP["aws_quicksight_custom_permissions"] --> RCP["aws_quicksight_role_custom_permission"]
 CP --> UCP["aws_quicksight_user_custom_permission"]
 GRP --> RM["aws_quicksight_role_membership"]

 ACCSUB["aws_quicksight_account_subscription"] --> ACCSET["aws_quicksight_account_settings"]
 ACCSUB -.->|account-wide gate| DSRC

 IPR["aws_quicksight_ip_restriction"]
 KEYR["aws_quicksight_key_registration"]

 style DSRC fill:#FF9900,color:#fff
Loading

ℹ️ Treatment depth is tiered. data_source, data_set, analysis, dashboard, folder/folder_membership, group/group_membership, user, vpc_connection, and refresh_schedule are deeply-typed and schema-complete. The eight account-singleton/permission-profile resources (account_subscription, account_settings, ip_restriction, key_registration, custom_permissions, role_custom_permission, role_membership, user_custom_permission) get lighter-but-honest treatment — every required field is modeled, just with fewer worked examples. See SCOPE.md for the full transparency note, including why raw definition authoring on analyses/dashboards/templates is out of scope.


✅ Provider / Versions

  • Terraform >= 1.12.0
  • hashicorp/aws >= 6.0, < 7.0
  • No provider {} block inside this module — the caller's provider configuration is inherited.

🔑 Required IAM Permissions

Action Required for Notes
quicksight:CreateAccountSubscription, DescribeAccountSubscription, DeleteAccountSubscription account_subscription One-time, account-wide gate
quicksight:CreateDataSource / Describe* / Update* / Delete* / *Permissions data_sources
quicksight:CreateDataSet / Describe* / Update* / Delete* / *Permissions data_sets Includes RLS/CLS grants
quicksight:CreateIngestion, DescribeIngestion, CancelIngestion ingestions
quicksight:CreateRefreshSchedule / Describe* / Update* / Delete* refresh_schedules
quicksight:CreateAnalysis / Describe* / Update* / Delete* / *Permissions analyses
quicksight:CreateDashboard / Describe* / Update* / Delete* / *Permissions, UpdateDashboardPublishedVersion dashboards
quicksight:CreateTemplate / Describe* / Update* / Delete* / *Permissions templates
quicksight:CreateTemplateAlias / Describe* / Update* / Delete* template_aliases
quicksight:CreateTheme / Describe* / Update* / Delete* / *Permissions themes
quicksight:CreateFolder / Describe* / Update* / Delete* / *Permissions folders
quicksight:CreateFolderMembership, DeleteFolderMembership folder_memberships
quicksight:CreateGroup, DescribeGroup, DeleteGroup groups
quicksight:CreateGroupMembership, DeleteGroupMembership group_memberships
quicksight:RegisterUser, DescribeUser, DeleteUser users
quicksight:CreateNamespace, DescribeNamespace, DeleteNamespace namespaces
quicksight:CreateIAMPolicyAssignment / Describe* / Update* / Delete* iam_policy_assignments
iam:PassRole vpc_connections[*].role_arn, data_sources[*].parameters.{s3,athena}.role_arn Scope to each specific role ARN — never *
quicksight:CreateVPCConnection / Describe* / Update* / Delete* vpc_connections
quicksight:UpdateIpRestriction, DescribeIpRestriction ip_restriction Account-wide singleton
quicksight:UpdateKeyRegistration, DescribeKeyRegistration key_registration Account-wide singleton
quicksight:CreateCustomPermissions / Describe* / Update* / Delete* custom_permissions
quicksight:UpdateRoleCustomPermission, DescribeRoleCustomPermission, DeleteRoleCustomPermission role_custom_permissions
quicksight:CreateRoleMembership, DeleteRoleMembership role_memberships
quicksight:UpdateUserCustomPermission, DescribeUserCustomPermission, DeleteUserCustomPermission user_custom_permissions

No service-linked role is auto-created by any resource in this module.


📋 AWS Prerequisites

  • Account subscription gates everything. Leave var.account_subscription = null if the account is already subscribed — re-subscribing an already-subscribed account fails.
  • Row-level and column-level security are the primary mechanisms for restricting who sees which rows/columns of financial/member data. This module does not enforce either automatically; data set owners must set row_level_permission_data_set / column_level_permission_rules explicitly, and Compliance/Risk should review the design for any dashboard exposing member financial data.
  • QuickSight account subscriptions are Region-specific — no cross-Region replica model.
  • SPICE capacity is a purchased, account-level quota distinct from Terraform's apply success — exceeding it fails ingestion.
  • VPC connections require ENI quota headroom in the target subnets.
  • ip_restriction / key_registration destroy clears the ENTIRE account's restrictions/registrations, not just what this module manages — treat as a security-relevant event.

📁 Module Structure

tf-mod-aws-quicksight/
├── providers.tf
├── variables.tf
├── main.tf
├── outputs.tf
├── README.md
└── SCOPE.md

⚙️ Quick Start

module "quicksight" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-quicksight?ref=v1.0.0"

  vpc_connections = {
    core = {
      name               = "core-vpc-connection"
      vpc_id             = module.vpc.id
      subnet_ids         = module.vpc.private_subnet_ids
      security_group_ids = [module.quicksight_sg.id]
      role_arn           = module.quicksight_vpc_role.arn
    }
  }

  data_sources = {
    member_redshift = {
      name = "Member Redshift"
      type = "REDSHIFT"
      parameters = {
        redshift = {
          cluster_id = "member-analytics-cluster"
          database   = "memberdata"
        }
      }
      vpc_connection_key = "core"
      credentials = {
        secret_arn = module.redshift_secret.arn
      }
    }
  }

  data_sets = {
    loan_portfolio = {
      name        = "Loan Portfolio"
      import_mode = "DIRECT_QUERY"
      physical_table_map = {
        loans = {
          relational_table = {
            data_source_arn = module.quicksight.data_source_arns["member_redshift"]
            name            = "loans"
            input_columns = [
              { name = "loan_id", type = "STRING" },
              { name = "officer_id", type = "STRING" },
              { name = "balance", type = "DECIMAL" },
            ]
          }
        }
      }
    }
  }

  tags = {
    Environment = "prod"
    Owner       = "analytics-platform"
  }
}

🔌 Cross-Module Contract

Consumes

Input Type Source module
vpc_connections[*].subnet_ids list(string) tf-mod-aws-vpc
vpc_connections[*].security_group_ids list(string) tf-mod-aws-security-group
vpc_connections[*].role_arn string tf-mod-aws-iam-role
data_sources[*].parameters.{s3,athena}.role_arn string tf-mod-aws-iam-role
data_sources[*].credentials.secret_arn string tf-mod-aws-secrets-manager
iam_policy_assignments[*].policy_arn string tf-mod-aws-iam-policy
key_registration.key_registration[*].key_arn string tf-mod-aws-kms

Emits

Output Description Consumed by
data_source_arns / data_source_ids Data source ARN/id maps data_sets[*].physical_table_map
data_set_arns / data_set_ids Data set ARN/id maps templates/analyses/dashboards data set references
analysis_arns Analysis ARN map templates[*].source_entity.source_analysis.arn
template_arns / template_version_numbers Template ARN/version maps template_aliases, analyses/dashboards source_template
dashboard_arns / dashboard_ids Dashboard ARN/id maps folder_memberships
theme_arns Theme ARN map analyses[*].theme_arn, dashboards[*].theme_arn
folder_arns / folder_ids Folder ARN/id maps Nested folders, folder_memberships
group_arns / user_arns Principal ARN maps RLS/CLS principals, permissions[*].principal
vpc_connection_arns VPC connection ARN map data_sources[*].vpc_connection_key
custom_permissions_arns Custom-permissions profile ARN map role_custom_permissions, user_custom_permissions

Full details, including the complete Consumes/Emits tables and every provider gotcha, are in SCOPE.md.


📚 Example Library

1 · Minimal S3 data source + data set
module "quicksight" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-quicksight?ref=v1.0.0"

  data_sources = {
    csv_uploads = {
      name = "CSV Uploads"
      type = "S3"
      parameters = {
        s3 = {
          manifest_file_location = {
            bucket = module.reports_bucket.id
            key    = "manifests/quicksight-manifest.json"
          }
        }
      }
    }
  }

  data_sets = {
    monthly_report = {
      name        = "Monthly Report"
      import_mode = "SPICE"
      physical_table_map = {
        report = {
          s3_source = {
            data_source_arn = module.quicksight.data_source_arns["csv_uploads"]
            input_columns = [
              { name = "period", type = "STRING" },
              { name = "amount", type = "DECIMAL" },
            ]
            upload_settings = { format = "CSV", contains_header = true }
          }
        }
      }
    }
  }
}
2 · Row-level security data set (member-identifiable financial data)
data_sets = {
  rls_rules = {
    name        = "RLS Rules"
    import_mode = "SPICE"
    use_as      = "RLS_RULES"
    physical_table_map = {
      rules = {
        s3_source = {
          data_source_arn = module.quicksight.data_source_arns["csv_uploads"]
          input_columns = [
            { name = "UserName", type = "STRING" },
            { name = "OfficerId", type = "STRING" },
          ]
          upload_settings = { format = "CSV", contains_header = true }
        }
      }
    }
  }

  loan_portfolio = {
    name        = "Loan Portfolio"
    import_mode = "DIRECT_QUERY"
    physical_table_map = {
      loans = {
        relational_table = {
          data_source_arn = module.quicksight.data_source_arns["member_redshift"]
          name            = "loans"
          input_columns = [
            { name = "loan_id", type = "STRING" },
            { name = "officer_id", type = "STRING" },
            { name = "balance", type = "DECIMAL" },
          ]
        }
      }
    }
    row_level_permission_data_set = {
      arn               = module.quicksight.data_set_arns["rls_rules"]
      permission_policy = "GRANT_ACCESS"
    }
    column_level_permission_rules = [
      {
        column_names = ["balance"]
        principals   = [module.quicksight.group_arns["loan-officers"]]
      }
    ]
  }
}
3 · Groups and users
groups = {
  loan-officers = { description = "Loan officer viewers" }
  analysts      = { description = "Data analysts / authors" }
}

group_memberships = {
  jsmith-officers = { group_name = "loan-officers", member_name = "jsmith" }
}

users = {
  jsmith = {
    email         = "jsmith@financialpartners.com"
    identity_type = "IAM"
    user_role     = "READER"
    iam_arn       = module.jsmith_role.arn
    session_name  = "jsmith"
  }
}
4 · VPC-connected private data source (secure-by-default)
vpc_connections = {
  core = {
    name               = "core-vpc-connection"
    vpc_id             = module.vpc.id
    subnet_ids         = module.vpc.private_subnet_ids
    security_group_ids = [module.quicksight_sg.id]
    role_arn           = module.quicksight_vpc_role.arn
  }
}

data_sources = {
  core_rds = {
    name = "Core RDS"
    type = "POSTGRESQL"
    parameters = {
      postgresql = {
        host     = module.rds.endpoint
        port     = 5432
        database = "coredb"
      }
    }
    vpc_connection_key = "core"
    credentials = {
      secret_arn = module.rds_secret.arn
    }
    ssl_properties = { disable_ssl = false }
  }
}
5 · Analysis promoted to a template, then to a dashboard
analyses = {
  loan_analysis = {
    name = "Loan Analysis (WIP)"
    source_entity = {
      source_template = {
        arn = data.aws_quicksight_template.starter.arn
        data_set_references = [{
          data_set_arn         = module.quicksight.data_set_arns["loan_portfolio"]
          data_set_placeholder = "loans"
        }]
      }
    }
    recovery_window_in_days = 30
  }
}

templates = {
  loan_template = {
    name                = "Loan Template"
    version_description = "v1"
    source_entity = {
      source_analysis = {
        arn = module.quicksight.analysis_arns["loan_analysis"]
        data_set_references = [{
          data_set_arn         = module.quicksight.data_set_arns["loan_portfolio"]
          data_set_placeholder = "loans"
        }]
      }
    }
  }
}

dashboards = {
  loan_dashboard = {
    name                = "Loan Portfolio Dashboard"
    version_description = "Initial publish"
    source_entity = {
      source_template = {
        arn = module.quicksight.template_arns["loan_template"]
        data_set_references = [{
          data_set_arn         = module.quicksight.data_set_arns["loan_portfolio"]
          data_set_placeholder = "loans"
        }]
      }
    }
  }
}
6 · Folder organization
folders = {
  finance = { name = "Finance" }
  loans   = { name = "Loans", parent_folder_key = "finance" }
}

folder_memberships = {
  loan_dashboard_in_loans = {
    folder_key  = "loans"
    member_type = "DASHBOARD"
    member_id   = module.quicksight.dashboard_ids["loan_dashboard"]
  }
}
7 · Refresh schedule (weekly incremental)
refresh_schedules = {
  loan_portfolio_weekly = {
    data_set_key = "loan_portfolio"
    refresh_type = "INCREMENTAL_REFRESH"
    schedule_frequency = {
      interval        = "WEEKLY"
      time_of_the_day = "05:00"
      timezone        = "America/Chicago"
      refresh_on_day  = { day_of_week = "MONDAY" }
    }
  }
}
8 · One-shot ingestion
ingestions = {
  loan_portfolio_now = {
    data_set_key   = "loan_portfolio"
    ingestion_type = "FULL_REFRESH"
  }
}
9 · `tags` example (merges with provider default_tags)
# Root provider block (outside this module):
# provider "aws" {
# default_tags { tags = { ManagedBy = "terraform", Team = "analytics-platform" } }
# }

module "quicksight" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-quicksight?ref=v1.0.0"

  tags = {
    Environment = "prod"
    CostCenter  = "1234"
    # A key collision with default_tags (e.g. Team) is won by this module's value.
  }
  #... resource maps...
}
10 · Customer-managed KMS key registration
key_registration = {
  key_registration = [
    { key_arn = module.quicksight_kms.arn, default_key = true }
  ]
}
11 · Account-wide IP restriction
ip_restriction = {
  enabled = true
  ip_restriction_rule_map = {
    "203.0.113.0/24" = "Corporate network"
  }
  vpc_id_restriction_rule_map = {
    (module.vpc.id) = "Core VPC endpoints"
  }
}
12 · Secure-by-default opt-out (public data source, no RLS — document the exception)
data_sources = {
  public_demo = {
    name       = "Public Demo Source"
    type       = "ATHENA"
    parameters = { athena = { work_group = "primary" } }
    # No vpc_connection_key => public internet path. Documented exception:
    # this data source is a non-sensitive public demo, not PII-bearing.
  }
}

data_sets = {
  demo_set = {
    name        = "Demo Set"
    import_mode = "DIRECT_QUERY"
    physical_table_map = {
      demo = {
        relational_table = {
          data_source_arn = module.quicksight.data_source_arns["public_demo"]
          name            = "demo_table"
          input_columns   = [{ name = "col1", type = "STRING" }]
        }
      }
    }
    # No row_level_permission_data_set / column_level_permission_rules — the
    # demo data set contains no member-identifiable data (documented exception).
  }
}
13 · `for_each` pattern (multiple analyses from a common template)
locals {
  regional_analyses = {
    midwest = { officer_group = "midwest-officers" }
    south   = { officer_group = "south-officers" }
  }
}

analyses = {
  for k, v in local.regional_analyses : k => {
    name = "Loan Analysis - ${k}"
    source_entity = {
      source_template = {
        arn = module.quicksight.template_arns["loan_template"]
        data_set_references = [{
          data_set_arn         = module.quicksight.data_set_arns["loan_portfolio"]
          data_set_placeholder = "loans"
        }]
      }
    }
    permissions = [{
      actions   = ["quicksight:DescribeAnalysis"]
      principal = module.quicksight.group_arns[v.officer_group]
    }]
  }
}
14 · Import an existing dashboard
import {
  to = module.quicksight.aws_quicksight_dashboard.this["loan_dashboard"]
  id = "123456789012,loan_dashboard"
}
15 · End-to-end composition (VPC → IAM role → RDS → QuickSight)
module "vpc" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-vpc?ref=v1.0.0"
  #...
}

module "quicksight_vpc_role" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-iam-role?ref=v1.0.0"
  name   = "quicksight-vpc-connection-role"
  assume_role_policy = {
    statements = [{
      actions    = ["sts:AssumeRole"]
      principals = [{ type = "Service", identifiers = ["quicksight.amazonaws.com"] }]
    }]
  }
}

module "rds" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-rds?ref=v1.0.0"
  #...
}

module "quicksight" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-quicksight?ref=v1.0.0"

  vpc_connections = {
    core = {
      name               = "core-vpc-connection"
      vpc_id             = module.vpc.id
      subnet_ids         = module.vpc.private_subnet_ids
      security_group_ids = [module.quicksight_sg.id]
      role_arn           = module.quicksight_vpc_role.arn
    }
  }

  data_sources = {
    core_rds = {
      name = "Core RDS"
      type = "POSTGRESQL"
      parameters = {
        postgresql = {
          host     = module.rds.endpoint
          port     = 5432
          database = "coredb"
        }
      }
      vpc_connection_key = "core"
      credentials        = { secret_arn = module.rds_secret.arn }
    }
  }

  data_sets = {
    core_data = {
      name        = "Core Data"
      import_mode = "DIRECT_QUERY"
      physical_table_map = {
        t = {
          relational_table = {
            data_source_arn = module.quicksight.data_source_arns["core_rds"]
            name            = "members"
            input_columns   = [{ name = "member_id", type = "STRING" }]
          }
        }
      }
      row_level_permission_data_set = {
        arn               = module.quicksight.data_set_arns["core_data_rls"]
        permission_policy = "GRANT_ACCESS"
      }
    }
  }

  tags = { Environment = "prod" }
}

📥 Inputs

  • Identity/Access: namespaces, groups, group_memberships, users, iam_policy_assignments, role_memberships, custom_permissions, role_custom_permissions, user_custom_permissions
  • Connectivity: vpc_connections
  • Data layer: data_sources, data_sets, ingestions, refresh_schedules
  • Presentation: themes, templates, template_aliases, analyses, dashboards
  • Organization: folders, folder_memberships
  • Account-wide: account_subscription, account_settings, ip_restriction, key_registration
  • Universal: tags, timeouts

Full per-field schemas with FORCE-NEW annotations are documented as heredoc descriptions directly in variables.tf.


🧾 Outputs

  • data_source_arns / data_source_ids, data_set_arns / data_set_ids
  • analysis_arns / analysis_ids, dashboard_arns / dashboard_ids
  • template_arns / template_version_numbers, template_alias_arns
  • theme_arns / theme_version_numbers
  • folder_arns / folder_ids, folder_membership_ids
  • group_arns, user_arns, user_ids, user_invitation_urls (sensitive — single-use registration links)
  • namespace_arns / namespace_ids
  • vpc_connection_arns / vpc_connection_ids / vpc_connection_availability_statuses
  • custom_permissions_arns
  • ingestion_arns / ingestion_statuses, refresh_schedule_arns
  • iam_policy_assignment_ids, group_membership_ids
  • account_subscription_status, account_settings_managed, ip_restriction_enabled, key_registration_managed
  • Per-collection *_tags_all maps (namespace_tags_all, vpc_connection_tags_all, data_source_tags_all, data_set_tags_all, theme_tags_all, template_tags_all, analysis_tags_all, dashboard_tags_all, folder_tags_all, custom_permissions_tags_all)

This module has no single keystone, so there is no single id/arn/tags_all output — every collection emits its own ARN/id/tags_all map instead.


🧠 Architecture Notes

  • ARN format: arn:aws:quicksight:<region>:<account-id>:<resource-type>/<resource-id> (e.g. .../dashboard/loan_dashboard).
  • Most map keys ARE the AWS-side identifier (data_source_id, data_set_id, analysis_id, dashboard_id, template_id, theme_id, folder_id, vpc_connection_id) and are therefore FORCE-NEW — renaming a map key destroys and recreates that resource.
  • tagstags_all ↔ provider default_tags: only a subset of the 25 resources accept tags at all (see variables.tf's tags variable description for the exact exclusion list — an AWS API limitation).
  • aws_quicksight_user cannot be imported.
  • aws_quicksight_refresh_schedule.schedule.schedule_frequency is required by this module (not optional as the raw schema nominally allows) — see Troubleshooting.
  • Destroy ordering: destroy folder_memberships before the member asset; destroy data_sources before their vpc_connection.
  • No us-east-1 global-resource requirement — QuickSight is a Region-scoped service (unlike CloudFront/ACM/WAFv2-CLOUDFRONT).

🧱 Design Principles

  • SSL enforced by default on data source connections where ssl_properties is set (disable_ssl = false). Opt out via disable_ssl = true only for sources that cannot terminate TLS.
  • Private VPC connectivity recommended for any PII-bearing data source (vpc_connection_key). Opt out by omitting it (public internet path).
  • Row-level security recommended on every data set with member-identifiable rows (row_level_permission_data_set). Opt out by omitting it.
  • Column-level security recommended for financial/PII columns (column_level_permission_rules). Opt out by omitting it.
  • Account IP allow-listing recommended (ip_restriction.enabled = true). Opt out by leaving var.ip_restriction = null.
  • Customer-managed KMS keys recommended (key_registration). Opt out by leaving var.key_registration = null (AWS-owned keys).
  • Account termination protection defaults to true. Opt out only with an approved decommission plan.
  • secret_arn recommended over credential_pair for data-source credentials — a credential_pair.password is plaintext in state.
  • Analysis deletion recovery window defaults to 30 days (the AWS maximum). Opt out down to 0 (immediate, unrecoverable) only with a documented exception.

🚀 Runbook

cd tf-mod-aws-quicksight
terraform init -backend=false
terraform validate
terraform fmt -check

⚠️ Pin the module source with ?ref=v1.0.0 — never a branch. terraform plan/apply require valid AWS credentials (profile/SSO/OIDC) and a Region; this module's local validation gate never runs apply.


🧪 Testing

  • terraform validate and terraform fmt -check pass with zero errors against hashicorp/aws v6.54.0.
  • No live-account testing is part of this authoring workflow — plan/apply against a non-production account with a least-privilege role is the recommended next step before promoting to v1.0.0 in a real environment.

💬 Example Output

$ terraform apply

 # module.quicksight.aws_quicksight_data_source.this["core_rds"] will be created
 # module.quicksight.aws_quicksight_data_set.this["core_data"] will be created
 # module.quicksight.aws_quicksight_dashboard.this["loan_dashboard"] will be created

Plan: 3 to add, 0 to change, 0 to destroy.

Outputs:

data_set_arns = {
 "core_data" = "arn:aws:quicksight:us-east-1:123456789012:dataset/core_data"
}
dashboard_arns = {
 "loan_dashboard" = "arn:aws:quicksight:us-east-1:123456789012:dashboard/loan_dashboard"
}

🔍 Troubleshooting

  • Tag drift from default_tags overlap — a resource-tag key duplicated in the provider's default_tags block causes perpetual diffs; resource tags win, check the *_tags_all output to see the merged result.
  • Credential-chain failuresAccessDenied/NoCredentialProviders mean the executing identity's AWS credential chain (profile/SSO/OIDC) isn't resolving; this module never handles credentials itself.
  • AccessDeniedException on CreateDataSource/CreateVPCConnection — check iam:PassRole is scoped to the exact role_arn passed, not a wildcard.
  • terraform validate fails with "Received null value, however the target type cannot handle null values" on aws_quicksight_refresh_schedule — this is why schedule_frequency is modeled as required in this module; always set it explicitly (see SCOPE.md provider gotchas).
  • InvalidParameterValueException on data source creation — usually a type/parameters mismatch (e.g. type = "S3" with parameters.athena set). This module's lifecycle precondition and validation blocks catch the most common cases at plan time, but double-check the sub-object matches type exactly.
  • Dashboard shows no data / blank visuals — check data_set_references placeholders in source_entity match the placeholder names baked into the source template exactly (case-sensitive).
  • Destroy fails on aws_quicksight_vpc_connection with ENIs still attached — destroy dependent data_sources referencing the connection first.
  • aws_quicksight_account_subscription destroy schedules deletion of the ENTIRE account — the single most destructive action available in this module; require explicit human sign-off before ever setting var.account_subscription = null on an existing subscription.

🔗 Related Docs

  • Terraform Registry: hashicorp/aws provider — QuickSight resource family (data source, data set, analysis, dashboard, template, theme, folder, group, user, namespace, VPC connection, refresh schedule, and related resources)
  • AWS documentation: Amazon QuickSight User Guide — row-level security, column-level security, VPC connections, SPICE
  • AWS documentation: Amazon QuickSight API Reference — CreateDataSource, CreateDataSet, CreateAccountSubscription

🧡 "Infrastructure as Code should be standardized, consistent, and secure."