diff --git a/CHANGELOG.md b/CHANGELOG.md index 0383507a186..45df5ab8e69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,10 @@ FEATURES +* **New Data Source:** `aws_lakeformation_data_lake_settings` [GH-13250] * **New Resource:** `aws_codestarconnections_connection` [GH-15990] -* **New Resource:** `aws_lakeformation_resource` ([#13267](https://github.com/hashicorp/terraform-provider-aws/issues/13267)) +* **New Resource:** `aws_lakeformation_data_lake_settings` [GH-13250] +* **New Resource:** `aws_lakeformation_resource` [GH-13267] ENHANCEMENTS diff --git a/aws/data_source_aws_lakeformation_data_lake_settings.go b/aws/data_source_aws_lakeformation_data_lake_settings.go new file mode 100644 index 00000000000..01e46336a6a --- /dev/null +++ b/aws/data_source_aws_lakeformation_data_lake_settings.go @@ -0,0 +1,105 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/lakeformation" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/hashcode" +) + +func dataSourceAwsLakeFormationDataLakeSettings() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsLakeFormationDataLakeSettingsRead, + + Schema: map[string]*schema.Schema{ + "catalog_id": { + Type: schema.TypeString, + Optional: true, + }, + "create_database_default_permissions": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "permissions": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "principal": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "create_table_default_permissions": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "permissions": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "principal": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "data_lake_admins": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "trusted_resource_owners": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceAwsLakeFormationDataLakeSettingsRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lakeformationconn + + input := &lakeformation.GetDataLakeSettingsInput{} + + if v, ok := d.GetOk("catalog_id"); ok { + input.CatalogId = aws.String(v.(string)) + } + d.SetId(fmt.Sprintf("%d", hashcode.String(input.String()))) + + output, err := conn.GetDataLakeSettings(input) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, lakeformation.ErrCodeEntityNotFoundException) { + log.Printf("[WARN] Lake Formation data lake settings (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading Lake Formation data lake settings (%s): %w", d.Id(), err) + } + + if output == nil || output.DataLakeSettings == nil { + return fmt.Errorf("error reading Lake Formation data lake settings (%s): empty response", d.Id()) + } + + settings := output.DataLakeSettings + + d.Set("create_database_default_permissions", flattenDataLakeSettingsCreateDefaultPermissions(settings.CreateDatabaseDefaultPermissions)) + d.Set("create_table_default_permissions", flattenDataLakeSettingsCreateDefaultPermissions(settings.CreateTableDefaultPermissions)) + d.Set("data_lake_admins", flattenDataLakeSettingsAdmins(settings.DataLakeAdmins)) + d.Set("trusted_resource_owners", flattenStringList(settings.TrustedResourceOwners)) + + return nil +} diff --git a/aws/data_source_aws_lakeformation_data_lake_settings_test.go b/aws/data_source_aws_lakeformation_data_lake_settings_test.go new file mode 100644 index 00000000000..61597552d58 --- /dev/null +++ b/aws/data_source_aws_lakeformation_data_lake_settings_test.go @@ -0,0 +1,56 @@ +package aws + +import ( + "testing" + + "github.com/aws/aws-sdk-go/service/lakeformation" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccAWSLakeFormationDataLakeSettingsDataSource_serial(t *testing.T) { + testCases := map[string]func(t *testing.T){ + "basic": testAccAWSLakeFormationDataLakeSettingsDataSource_basic, + // if more tests are added, they should be serial (data catalog is account-shared resource) + } + + for name, tc := range testCases { + tc := tc + t.Run(name, func(t *testing.T) { + tc(t) + }) + } +} + +func testAccAWSLakeFormationDataLakeSettingsDataSource_basic(t *testing.T) { + callerIdentityName := "data.aws_caller_identity.current" + resourceName := "data.aws_lakeformation_data_lake_settings.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck(lakeformation.EndpointsID, t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLakeFormationDataLakeSettingsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLakeFormationDataLakeSettingsDataSourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "catalog_id", callerIdentityName, "account_id"), + resource.TestCheckResourceAttr(resourceName, "data_lake_admins.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "data_lake_admins.0", callerIdentityName, "arn"), + ), + }, + }, + }) +} + +const testAccAWSLakeFormationDataLakeSettingsDataSourceConfig_basic = ` +data "aws_caller_identity" "current" {} + +resource "aws_lakeformation_data_lake_settings" "test" { + catalog_id = data.aws_caller_identity.current.account_id + data_lake_admins = [data.aws_caller_identity.current.arn] +} + +data "aws_lakeformation_data_lake_settings" "test" { + catalog_id = aws_lakeformation_data_lake_settings.test.catalog_id +} +` diff --git a/aws/provider.go b/aws/provider.go index b67cfbd35dc..9eb5c30013f 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -271,11 +271,11 @@ func Provider() *schema.Provider { "aws_imagebuilder_image_pipeline": dataSourceAwsImageBuilderImagePipeline(), "aws_imagebuilder_image_recipe": dataSourceAwsImageBuilderImageRecipe(), "aws_imagebuilder_infrastructure_configuration": datasourceAwsImageBuilderInfrastructureConfiguration(), - "aws_internet_gateway": dataSourceAwsInternetGateway(), - "aws_iot_endpoint": dataSourceAwsIotEndpoint(), "aws_inspector_rules_packages": dataSourceAwsInspectorRulesPackages(), "aws_instance": dataSourceAwsInstance(), "aws_instances": dataSourceAwsInstances(), + "aws_internet_gateway": dataSourceAwsInternetGateway(), + "aws_iot_endpoint": dataSourceAwsIotEndpoint(), "aws_ip_ranges": dataSourceAwsIPRanges(), "aws_kinesis_stream": dataSourceAwsKinesisStream(), "aws_kms_alias": dataSourceAwsKmsAlias(), @@ -283,6 +283,7 @@ func Provider() *schema.Provider { "aws_kms_key": dataSourceAwsKmsKey(), "aws_kms_secret": dataSourceAwsKmsSecret(), "aws_kms_secrets": dataSourceAwsKmsSecrets(), + "aws_lakeformation_data_lake_settings": dataSourceAwsLakeFormationDataLakeSettings(), "aws_lambda_alias": dataSourceAwsLambdaAlias(), "aws_lambda_code_signing_config": dataSourceAwsLambdaCodeSigningConfig(), "aws_lambda_function": dataSourceAwsLambdaFunction(), @@ -290,8 +291,8 @@ func Provider() *schema.Provider { "aws_lambda_layer_version": dataSourceAwsLambdaLayerVersion(), "aws_launch_configuration": dataSourceAwsLaunchConfiguration(), "aws_launch_template": dataSourceAwsLaunchTemplate(), - "aws_lex_bot": dataSourceAwsLexBot(), "aws_lex_bot_alias": dataSourceAwsLexBotAlias(), + "aws_lex_bot": dataSourceAwsLexBot(), "aws_lex_intent": dataSourceAwsLexIntent(), "aws_lex_slot_type": dataSourceAwsLexSlotType(), "aws_mq_broker": dataSourceAwsMqBroker(), @@ -743,6 +744,7 @@ func Provider() *schema.Provider { "aws_kms_grant": resourceAwsKmsGrant(), "aws_kms_key": resourceAwsKmsKey(), "aws_kms_ciphertext": resourceAwsKmsCiphertext(), + "aws_lakeformation_data_lake_settings": resourceAwsLakeFormationDataLakeSettings(), "aws_lakeformation_resource": resourceAwsLakeFormationResource(), "aws_lambda_alias": resourceAwsLambdaAlias(), "aws_lambda_code_signing_config": resourceAwsLambdaCodeSigningConfig(), diff --git a/aws/resource_aws_lakeformation_data_lake_settings.go b/aws/resource_aws_lakeformation_data_lake_settings.go new file mode 100644 index 00000000000..51463b4b756 --- /dev/null +++ b/aws/resource_aws_lakeformation_data_lake_settings.go @@ -0,0 +1,290 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/lakeformation" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "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/hashcode" +) + +func resourceAwsLakeFormationDataLakeSettings() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsLakeFormationDataLakeSettingsCreate, + Update: resourceAwsLakeFormationDataLakeSettingsCreate, + Read: resourceAwsLakeFormationDataLakeSettingsRead, + Delete: resourceAwsLakeFormationDataLakeSettingsDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "catalog_id": { + Type: schema.TypeString, + ForceNew: true, + Optional: true, + }, + "create_database_default_permissions": { + Type: schema.TypeList, + Computed: true, + Optional: true, + MaxItems: 3, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "permissions": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(lakeformation.Permission_Values(), false), + }, + }, + "principal": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.NoZeroValues, // can be non-ARN, e.g. "IAM_ALLOWED_PRINCIPALS" + }, + }, + }, + }, + "create_table_default_permissions": { + Type: schema.TypeList, + Computed: true, + Optional: true, + MaxItems: 3, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "permissions": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(lakeformation.Permission_Values(), false), + }, + }, + "principal": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.NoZeroValues, // can be non-ARN, e.g. "IAM_ALLOWED_PRINCIPALS" + }, + }, + }, + }, + "data_lake_admins": { + Type: schema.TypeList, + Computed: true, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateArn, + }, + }, + "trusted_resource_owners": { + Type: schema.TypeList, + Computed: true, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateAwsAccountId, + }, + }, + }, + } +} + +func resourceAwsLakeFormationDataLakeSettingsCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lakeformationconn + + input := &lakeformation.PutDataLakeSettingsInput{} + + if v, ok := d.GetOk("catalog_id"); ok { + input.CatalogId = aws.String(v.(string)) + } + + settings := &lakeformation.DataLakeSettings{} + + if v, ok := d.GetOk("create_database_default_permissions"); ok { + settings.CreateDatabaseDefaultPermissions = expandDataLakeSettingsCreateDefaultPermissions(v.([]interface{})) + } + + if v, ok := d.GetOk("create_table_default_permissions"); ok { + settings.CreateTableDefaultPermissions = expandDataLakeSettingsCreateDefaultPermissions(v.([]interface{})) + } + + if v, ok := d.GetOk("data_lake_admins"); ok { + settings.DataLakeAdmins = expandDataLakeSettingsAdmins(v.([]interface{})) + } + + if v, ok := d.GetOk("trusted_resource_owners"); ok { + settings.TrustedResourceOwners = expandStringList(v.([]interface{})) + } + + input.DataLakeSettings = settings + output, err := conn.PutDataLakeSettings(input) + + if err != nil { + return fmt.Errorf("error creating Lake Formation data lake settings: %w", err) + } + + if output == nil { + return fmt.Errorf("error creating Lake Formation data lake settings: empty response") + } + + d.SetId(fmt.Sprintf("%d", hashcode.String(input.String()))) + + return resourceAwsLakeFormationDataLakeSettingsRead(d, meta) +} + +func resourceAwsLakeFormationDataLakeSettingsRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lakeformationconn + + input := &lakeformation.GetDataLakeSettingsInput{} + + if v, ok := d.GetOk("catalog_id"); ok { + input.CatalogId = aws.String(v.(string)) + } + + output, err := conn.GetDataLakeSettings(input) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, lakeformation.ErrCodeEntityNotFoundException) { + log.Printf("[WARN] Lake Formation data lake settings (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading Lake Formation data lake settings (%s): %w", d.Id(), err) + } + + if output == nil || output.DataLakeSettings == nil { + return fmt.Errorf("error reading Lake Formation data lake settings (%s): empty response", d.Id()) + } + + settings := output.DataLakeSettings + + d.Set("create_database_default_permissions", flattenDataLakeSettingsCreateDefaultPermissions(settings.CreateDatabaseDefaultPermissions)) + d.Set("create_table_default_permissions", flattenDataLakeSettingsCreateDefaultPermissions(settings.CreateTableDefaultPermissions)) + d.Set("data_lake_admins", flattenDataLakeSettingsAdmins(settings.DataLakeAdmins)) + d.Set("trusted_resource_owners", flattenStringList(settings.TrustedResourceOwners)) + + return nil +} + +func resourceAwsLakeFormationDataLakeSettingsDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lakeformationconn + + input := &lakeformation.PutDataLakeSettingsInput{ + DataLakeSettings: &lakeformation.DataLakeSettings{ + CreateDatabaseDefaultPermissions: make([]*lakeformation.PrincipalPermissions, 0), + CreateTableDefaultPermissions: make([]*lakeformation.PrincipalPermissions, 0), + DataLakeAdmins: make([]*lakeformation.DataLakePrincipal, 0), + TrustedResourceOwners: make([]*string, 0), + }, + } + + if v, ok := d.GetOk("catalog_id"); ok { + input.CatalogId = aws.String(v.(string)) + } + + _, err := conn.PutDataLakeSettings(input) + + if tfawserr.ErrCodeEquals(err, lakeformation.ErrCodeEntityNotFoundException) { + log.Printf("[WARN] Lake Formation data lake settings (%s) not found, removing from state", d.Id()) + return nil + } + + if err != nil { + return fmt.Errorf("error deleting Lake Formation data lake settings (%s): %w", d.Id(), err) + } + + return nil +} + +func expandDataLakeSettingsCreateDefaultPermissions(tfMaps []interface{}) []*lakeformation.PrincipalPermissions { + apiObjects := make([]*lakeformation.PrincipalPermissions, 0, len(tfMaps)) + + for _, tfMap := range tfMaps { + apiObjects = append(apiObjects, expandDataLakeSettingsCreateDefaultPermission(tfMap.(map[string]interface{}))) + } + + return apiObjects +} + +func expandDataLakeSettingsCreateDefaultPermission(tfMap map[string]interface{}) *lakeformation.PrincipalPermissions { + apiObject := &lakeformation.PrincipalPermissions{ + Permissions: expandStringSet(tfMap["permissions"].(*schema.Set)), + Principal: &lakeformation.DataLakePrincipal{ + DataLakePrincipalIdentifier: aws.String(tfMap["principal"].(string)), + }, + } + + return apiObject +} + +func flattenDataLakeSettingsCreateDefaultPermissions(apiObjects []*lakeformation.PrincipalPermissions) []map[string]interface{} { + if apiObjects == nil { + return nil + } + + tfMaps := make([]map[string]interface{}, len(apiObjects)) + for i, v := range apiObjects { + tfMaps[i] = flattenDataLakeSettingsCreateDefaultPermission(v) + } + + return tfMaps +} + +func flattenDataLakeSettingsCreateDefaultPermission(apiObject *lakeformation.PrincipalPermissions) map[string]interface{} { + tfMap := make(map[string]interface{}) + + if apiObject == nil { + return tfMap + } + + if apiObject.Permissions != nil { + tfMap["permissions"] = flattenStringSet(apiObject.Permissions) + } + + if v := aws.StringValue(apiObject.Principal.DataLakePrincipalIdentifier); v != "" { + tfMap["principal"] = v + } + + return tfMap +} + +func expandDataLakeSettingsAdmins(tfSlice []interface{}) []*lakeformation.DataLakePrincipal { + apiObjects := make([]*lakeformation.DataLakePrincipal, 0, len(tfSlice)) + + for _, tfItem := range tfSlice { + val, ok := tfItem.(string) + if ok && val != "" { + apiObjects = append(apiObjects, &lakeformation.DataLakePrincipal{ + DataLakePrincipalIdentifier: aws.String(tfItem.(string)), + }) + } + } + + return apiObjects +} + +func flattenDataLakeSettingsAdmins(apiObjects []*lakeformation.DataLakePrincipal) []interface{} { + if apiObjects == nil { + return nil + } + + tfSlice := make([]interface{}, 0, len(apiObjects)) + + for _, apiObject := range apiObjects { + tfSlice = append(tfSlice, *apiObject.DataLakePrincipalIdentifier) + } + + return tfSlice +} diff --git a/aws/resource_aws_lakeformation_data_lake_settings_test.go b/aws/resource_aws_lakeformation_data_lake_settings_test.go new file mode 100644 index 00000000000..6c2c358a526 --- /dev/null +++ b/aws/resource_aws_lakeformation_data_lake_settings_test.go @@ -0,0 +1,176 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/lakeformation" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccAWSLakeFormationDataLakeSettings_serial(t *testing.T) { + testCases := map[string]func(t *testing.T){ + "basic": testAccAWSLakeFormationDataLakeSettings_basic, + "disappears": testAccAWSLakeFormationDataLakeSettings_disappears, + "withoutCatalogId": testAccAWSLakeFormationDataLakeSettings_withoutCatalogId, + } + + for name, tc := range testCases { + tc := tc + t.Run(name, func(t *testing.T) { + tc(t) + }) + } +} + +func testAccAWSLakeFormationDataLakeSettings_basic(t *testing.T) { + callerIdentityName := "data.aws_caller_identity.current" + resourceName := "aws_lakeformation_data_lake_settings.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck(lakeformation.EndpointsID, t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLakeFormationDataLakeSettingsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLakeFormationDataLakeSettingsConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSLakeFormationDataLakeSettingsExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "catalog_id", callerIdentityName, "account_id"), + resource.TestCheckResourceAttr(resourceName, "data_lake_admins.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "data_lake_admins.0", callerIdentityName, "arn"), + ), + }, + }, + }) +} + +func testAccAWSLakeFormationDataLakeSettings_disappears(t *testing.T) { + resourceName := "aws_lakeformation_data_lake_settings.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck(lakeformation.EndpointsID, t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLakeFormationDataLakeSettingsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLakeFormationDataLakeSettingsConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSLakeFormationDataLakeSettingsExists(resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsLakeFormationDataLakeSettings(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccAWSLakeFormationDataLakeSettings_withoutCatalogId(t *testing.T) { + callerIdentityName := "data.aws_caller_identity.current" + resourceName := "aws_lakeformation_data_lake_settings.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLakeFormationDataLakeSettingsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLakeFormationDataLakeSettingsConfig_withoutCatalogId, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSLakeFormationDataLakeSettingsExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "data_lake_admins.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "data_lake_admins.0", callerIdentityName, "arn"), + ), + }, + }, + }) +} + +func testAccCheckAWSLakeFormationDataLakeSettingsDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).lakeformationconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_lakeformation_data_lake_settings" { + continue + } + + input := &lakeformation.GetDataLakeSettingsInput{} + + if rs.Primary.Attributes["catalog_id"] != "" { + input.CatalogId = aws.String(rs.Primary.Attributes["catalog_id"]) + } + + output, err := conn.GetDataLakeSettings(input) + + if tfawserr.ErrCodeEquals(err, lakeformation.ErrCodeEntityNotFoundException) { + continue + } + + if err != nil { + return fmt.Errorf("error getting Lake Formation data lake settings (%s): %w", rs.Primary.ID, err) + } + + if output != nil && output.DataLakeSettings != nil && len(output.DataLakeSettings.DataLakeAdmins) > 0 { + return fmt.Errorf("Lake Formation data lake admin(s) (%s) still exist", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckAWSLakeFormationDataLakeSettingsExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("resource not found: %s", resourceName) + } + + conn := testAccProvider.Meta().(*AWSClient).lakeformationconn + + input := &lakeformation.GetDataLakeSettingsInput{} + + if rs.Primary.Attributes["catalog_id"] != "" { + input.CatalogId = aws.String(rs.Primary.Attributes["catalog_id"]) + } + + _, err := conn.GetDataLakeSettings(input) + + if err != nil { + return fmt.Errorf("error getting Lake Formation data lake settings (%s): %w", rs.Primary.ID, err) + } + + return nil + } +} + +const testAccAWSLakeFormationDataLakeSettingsConfig_basic = ` +data "aws_caller_identity" "current" {} + +resource "aws_lakeformation_data_lake_settings" "test" { + catalog_id = data.aws_caller_identity.current.account_id + + create_database_default_permissions { + principal = "IAM_ALLOWED_PRINCIPALS" + permissions = ["ALL"] + } + + create_table_default_permissions { + principal = "IAM_ALLOWED_PRINCIPALS" + permissions = ["ALL"] + } + + data_lake_admins = [data.aws_caller_identity.current.arn] + trusted_resource_owners = [data.aws_caller_identity.current.account_id] +} +` + +const testAccAWSLakeFormationDataLakeSettingsConfig_withoutCatalogId = ` +data "aws_caller_identity" "current" {} + +resource "aws_lakeformation_data_lake_settings" "test" { + data_lake_admins = [data.aws_caller_identity.current.arn] +} +` diff --git a/docs/FAQ.md b/docs/FAQ.md index 04973301df1..84ed2d8762a 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -10,6 +10,7 @@ The HashiCorp Terraform AWS provider team is : * Brian Flad, Engineering Lead - GitHub [@bflad](https://github.com/bflad) * Graham Davison, Engineer - GitHub [@gdavison](https://github.com/gdavison) * Angie Pinilla, Engineer - GitHub [@angie44](https://github.com/angie44) +* Dirk Avery (Federal), Engineer - GitHub [@YakDriver](https://github.com/yakdriver) * Bill Rich, Engineer - GitHub [@bill-rich](https://github.com/bill-rich) * Simon Davis, Engineering Manager - GitHub [@breathingdust](https://github.com/breathingdust) * Kerim Satirli, Developer Advocate - GitHub [@ksatirli](https://github.com/ksatirli) diff --git a/docs/roadmaps/2020_August_to_October.md b/docs/roadmaps/2020_August_to_October.md index 6ea8ac4413f..7408976a71c 100644 --- a/docs/roadmaps/2020_August_to_October.md +++ b/docs/roadmaps/2020_August_to_October.md @@ -54,7 +54,7 @@ Support for AWS Lake Formation will include: New Resource(s): - aws_lakeformation_resource -- aws_lakeformation_datalake_settings +- aws_lakeformation_data_lake_settings - aws_lakeformation_permissions ### AWS Serverless Application Repository diff --git a/website/docs/d/lakeformation_data_lake_settings.html.markdown b/website/docs/d/lakeformation_data_lake_settings.html.markdown new file mode 100644 index 00000000000..7c0640a2a2a --- /dev/null +++ b/website/docs/d/lakeformation_data_lake_settings.html.markdown @@ -0,0 +1,44 @@ +--- +subcategory: "Lake Formation" +layout: "aws" +page_title: "AWS: aws_lakeformation_data_lake_settings" +description: |- + Get data lake administrators and default database and table permissions +--- + +# Data Source: aws_lakeformation_data_lake_settings + +Get Lake Formation principals designated as data lake administrators and lists of principal permission entries for default create database and default create table permissions. + +## Example Usage + +```hcl +data "aws_lakeformation_data_lake_settings" "example" { + catalog_id = "14916253649" +} +``` + +## Argument Reference + +The following arguments are optional: + +* `catalog_id` – (Optional) Identifier for the Data Catalog. By default, the account ID. + +## Attributes Reference + +In addition to arguments above, the following attributes are exported. + +* `create_database_default_permissions` - Up to three configuration blocks of principal permissions for default create database permissions. Detailed below. +* `create_table_default_permissions` - Up to three configuration blocks of principal permissions for default create table permissions. Detailed below. +* `data_lake_admins` – List of ARNs of AWS Lake Formation principals (IAM users or roles). +* `trusted_resource_owners` – List of the resource-owning account IDs that the caller's account can use to share their user access details (user ARNs). + +### create_database_default_permissions + +* `permissions` - List of permissions granted to the principal. Valid values include `ALL`, `SELECT`, `ALTER`, `DROP`, `DELETE`, `INSERT`, `DESCRIBE`, `CREATE_DATABASE`, `CREATE_TABLE`, and `DATA_LOCATION_ACCESS`. +* `principal` - Principal who is granted permissions. + +### create_table_default_permissions + +* `permissions` - List of permissions granted to the principal. Valid values include `ALL`, `SELECT`, `ALTER`, `DROP`, `DELETE`, `INSERT`, `DESCRIBE`, `CREATE_DATABASE`, `CREATE_TABLE`, and `DATA_LOCATION_ACCESS`. +* `principal` - Principal who is granted permissions. diff --git a/website/docs/r/lakeformation_data_lake_settings.html.markdown b/website/docs/r/lakeformation_data_lake_settings.html.markdown new file mode 100644 index 00000000000..6da510981c7 --- /dev/null +++ b/website/docs/r/lakeformation_data_lake_settings.html.markdown @@ -0,0 +1,69 @@ +--- +subcategory: "Lake Formation" +layout: "aws" +page_title: "AWS: aws_lakeformation_data_lake_settings" +description: |- + Manages data lake administrators and default database and table permissions +--- + +# Resource: aws_lakeformation_data_lake_settings + +Manages Lake Formation principals designated as data lake administrators and lists of principal permission entries for default create database and default create table permissions. + +~> **NOTE:** Lake Formation introduces fine-grained access control for data in your data lake. Part of the changes include the `IAMAllowedPrincipals` principal in order to make Lake Formation backwards compatible with existing IAM and Glue permissions. For more information, see [Changing the Default Security Settings for Your Data Lake](https://docs.aws.amazon.com/lake-formation/latest/dg/change-settings.html) and [Upgrading AWS Glue Data Permissions to the AWS Lake Formation Model](https://docs.aws.amazon.com/lake-formation/latest/dg/upgrade-glue-lake-formation.html). + +## Example Usage + +### Data Lake Admins + +```hcl +resource "aws_lakeformation_data_lake_settings" "example" { + data_lake_admins = [aws_iam_user.test.arn, aws_iam_role.test.arn] +} +``` + +### Create Default Permissions + +```hcl +resource "aws_lakeformation_data_lake_settings" "example" { + data_lake_admins = [aws_iam_user.test.arn, aws_iam_role.test.arn] + + create_database_default_permissions { + permissions = ["SELECT", "ALTER", "DROP"] + principal = aws_iam_user.test.arn + } + + create_table_default_permissions { + permissions = ["ALL"] + principal = aws_iam_role.test.arn + } +} +``` + +## Argument Reference + +The following arguments are optional: + +* `catalog_id` – (Optional) Identifier for the Data Catalog. By default, the account ID. +* `create_database_default_permissions` - (Optional) Up to three configuration blocks of principal permissions for default create database permissions. Detailed below. +* `create_table_default_permissions` - (Optional) Up to three configuration blocks of principal permissions for default create table permissions. Detailed below. +* `data_lake_admins` – (Optional) List of ARNs of AWS Lake Formation principals (IAM users or roles). +* `trusted_resource_owners` – (Optional) List of the resource-owning account IDs that the caller's account can use to share their user access details (user ARNs). + +### create_database_default_permissions + +The following arguments are optional: + +* `permissions` - (Optional) List of permissions that are granted to the principal. Valid values include `ALL`, `SELECT`, `ALTER`, `DROP`, `DELETE`, `INSERT`, `DESCRIBE`, `CREATE_DATABASE`, `CREATE_TABLE`, and `DATA_LOCATION_ACCESS`. +* `principal` - (Optional) Principal who is granted permissions. To enforce metadata and underlying data access control only by IAM on new databases and tables set `principal` to `IAM_ALLOWED_PRINCIPALS` and `permissions` to `["ALL"]`. + +### create_table_default_permissions + +The following arguments are optional: + +* `permissions` - (Optional) List of permissions that are granted to the principal. Valid values include `ALL`, `SELECT`, `ALTER`, `DROP`, `DELETE`, `INSERT`, `DESCRIBE`, `CREATE_DATABASE`, `CREATE_TABLE`, and `DATA_LOCATION_ACCESS`. +* `principal` - (Optional) Principal who is granted permissions. To enforce metadata and underlying data access control only by IAM on new databases and tables set `principal` to `IAM_ALLOWED_PRINCIPALS` and `permissions` to `["ALL"]`. + +## Attributes Reference + +In addition to all arguments above, no attributes are exported.