diff --git a/CHANGELOG.md b/CHANGELOG.md index 0354baa6db9..5a289ec5024 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,12 @@ FEATURES * **New Data Source:** `aws_ec2_managed_prefix_list` [GH-16738] * **New Data Source:** `aws_lakeformation_data_lake_settings` [GH-13250] +* **New Data Source:** `aws_lakeformation_permissions` [GH-13396] +* **New Data Source:** `aws_lakeformation_resource` [GH-13396] * **New Resource:** `aws_codestarconnections_connection` [GH-15990] * **New Resource:** `aws_ec2_managed_prefix_list` [GH-14068] * **New Resource:** `aws_lakeformation_data_lake_settings` [GH-13250] +* **New Resource:** `aws_lakeformation_permissions` [GH-13396] * **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 index 01e46336a6a..27244797a9c 100644 --- a/aws/data_source_aws_lakeformation_data_lake_settings.go +++ b/aws/data_source_aws_lakeformation_data_lake_settings.go @@ -16,6 +16,11 @@ func dataSourceAwsLakeFormationDataLakeSettings() *schema.Resource { Read: dataSourceAwsLakeFormationDataLakeSettingsRead, Schema: map[string]*schema.Schema{ + "admins": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, "catalog_id": { Type: schema.TypeString, Optional: true, @@ -54,11 +59,6 @@ func dataSourceAwsLakeFormationDataLakeSettings() *schema.Resource { }, }, }, - "data_lake_admins": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, "trusted_resource_owners": { Type: schema.TypeList, Computed: true, @@ -98,7 +98,7 @@ func dataSourceAwsLakeFormationDataLakeSettingsRead(d *schema.ResourceData, meta 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("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 index 61597552d58..e0ae83a99b4 100644 --- a/aws/data_source_aws_lakeformation_data_lake_settings_test.go +++ b/aws/data_source_aws_lakeformation_data_lake_settings_test.go @@ -7,20 +7,6 @@ import ( "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" @@ -34,8 +20,8 @@ func testAccAWSLakeFormationDataLakeSettingsDataSource_basic(t *testing.T) { 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"), + resource.TestCheckResourceAttr(resourceName, "admins.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "admins.0", callerIdentityName, "arn"), ), }, }, @@ -46,8 +32,8 @@ 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] + catalog_id = data.aws_caller_identity.current.account_id + admins = [data.aws_caller_identity.current.arn] } data "aws_lakeformation_data_lake_settings" "test" { diff --git a/aws/data_source_aws_lakeformation_permissions.go b/aws/data_source_aws_lakeformation_permissions.go new file mode 100644 index 00000000000..7773b8a552e --- /dev/null +++ b/aws/data_source_aws_lakeformation_permissions.go @@ -0,0 +1,266 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "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/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/hashcode" +) + +func dataSourceAwsLakeFormationPermissions() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsLakeFormationPermissionsRead, + + Schema: map[string]*schema.Schema{ + "catalog_id": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateAwsAccountId, + }, + "catalog_resource": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "data_location": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateArn, + }, + "catalog_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validateAwsAccountId, + }, + }, + }, + }, + "database": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "catalog_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validateAwsAccountId, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "permissions": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "permissions_with_grant_option": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "principal": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validatePrincipal, + }, + "table": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "catalog_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validateAwsAccountId, + }, + "database_name": { + Type: schema.TypeString, + Required: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "wildcard": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + }, + }, + }, + "table_with_columns": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "catalog_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validateAwsAccountId, + }, + "column_names": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.NoZeroValues, + }, + }, + "database_name": { + Type: schema.TypeString, + Required: true, + }, + "excluded_column_names": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.NoZeroValues, + }, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceAwsLakeFormationPermissionsRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lakeformationconn + + input := &lakeformation.ListPermissionsInput{ + Principal: &lakeformation.DataLakePrincipal{ + DataLakePrincipalIdentifier: aws.String(d.Get("principal").(string)), + }, + } + + if v, ok := d.GetOk("catalog_id"); ok { + input.CatalogId = aws.String(v.(string)) + } + + input.Resource = expandLakeFormationResource(d, true) + + log.Printf("[DEBUG] Reading Lake Formation permissions: %v", input) + var principalResourcePermissions []*lakeformation.PrincipalResourcePermissions + + err := resource.Retry(2*time.Minute, func() *resource.RetryError { + err := conn.ListPermissionsPages(input, func(resp *lakeformation.ListPermissionsOutput, lastPage bool) bool { + for _, permission := range resp.PrincipalResourcePermissions { + if permission == nil { + continue + } + + principalResourcePermissions = append(principalResourcePermissions, permission) + } + return !lastPage + }) + + if err != nil { + if isAWSErr(err, lakeformation.ErrCodeInvalidInputException, "Invalid principal") { + return resource.RetryableError(err) + } + return resource.NonRetryableError(fmt.Errorf("error reading Lake Formation Permissions: %w", err)) + } + return nil + }) + + if isResourceTimeoutError(err) { + err = conn.ListPermissionsPages(input, func(resp *lakeformation.ListPermissionsOutput, lastPage bool) bool { + for _, permission := range resp.PrincipalResourcePermissions { + if permission == nil { + continue + } + + principalResourcePermissions = append(principalResourcePermissions, permission) + } + return !lastPage + }) + } + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, lakeformation.ErrCodeEntityNotFoundException) { + log.Printf("[WARN] Resource Lake Formation permissions (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading Lake Formation permissions: %w", err) + } + + if len(principalResourcePermissions) > 1 { + return fmt.Errorf("error reading Lake Formation permissions: %s", "multiple permissions found") + } + + d.SetId(fmt.Sprintf("%d", hashcode.String(input.String()))) + for _, permissions := range principalResourcePermissions { + d.Set("principal", permissions.Principal.DataLakePrincipalIdentifier) + d.Set("permissions", permissions.Permissions) + d.Set("permissions_with_grant_option", permissions.PermissionsWithGrantOption) + + if permissions.Resource.Catalog != nil { + d.Set("catalog_resource", true) + } + + if permissions.Resource.DataLocation != nil { + d.Set("data_location", []interface{}{flattenLakeFormationDataLocationResource(permissions.Resource.DataLocation)}) + } else { + d.Set("data_location", nil) + } + + if permissions.Resource.Database != nil { + d.Set("database", []interface{}{flattenLakeFormationDatabaseResource(permissions.Resource.Database)}) + } else { + d.Set("database", nil) + } + + // table with columns permissions will include the table and table with columns + if permissions.Resource.TableWithColumns != nil { + d.Set("table_with_columns", []interface{}{flattenLakeFormationTableWithColumnsResource(permissions.Resource.TableWithColumns)}) + } else if permissions.Resource.Table != nil { + d.Set("table_with_columns", nil) + d.Set("table", []interface{}{flattenLakeFormationTableResource(permissions.Resource.Table)}) + } else { + d.Set("table", nil) + } + } + + return nil +} diff --git a/aws/data_source_aws_lakeformation_permissions_test.go b/aws/data_source_aws_lakeformation_permissions_test.go new file mode 100644 index 00000000000..4b59b28c8c8 --- /dev/null +++ b/aws/data_source_aws_lakeformation_permissions_test.go @@ -0,0 +1,445 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/lakeformation" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func testAccAWSLakeFormationPermissionsDataSource_basic(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lakeformation_permissions.test" + dataSourceName := "data.aws_lakeformation_permissions.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck(lakeformation.EndpointsID, t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLakeFormationPermissionsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLakeFormationPermissionsDataSourceConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "principal", dataSourceName, "principal"), + resource.TestCheckResourceAttrPair(resourceName, "permissions.#", dataSourceName, "permissions.#"), + resource.TestCheckResourceAttrPair(resourceName, "permissions.0", dataSourceName, "permissions.0"), + resource.TestCheckResourceAttrPair(resourceName, "catalog_resource", dataSourceName, "catalog_resource"), + ), + }, + }, + }) +} + +func testAccAWSLakeFormationPermissionsDataSource_dataLocation(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lakeformation_permissions.test" + dataSourceName := "data.aws_lakeformation_permissions.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck(lakeformation.EndpointsID, t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLakeFormationPermissionsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLakeFormationPermissionsDataSourceConfig_dataLocation(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "principal", dataSourceName, "principal"), + resource.TestCheckResourceAttrPair(resourceName, "permissions.#", dataSourceName, "permissions.#"), + resource.TestCheckResourceAttrPair(resourceName, "permissions.0", dataSourceName, "permissions.0"), + resource.TestCheckResourceAttrPair(resourceName, "data_location.#", dataSourceName, "data_location.#"), + resource.TestCheckResourceAttrPair(resourceName, "data_location.0.arn", dataSourceName, "data_location.0.arn"), + ), + }, + }, + }) +} + +func testAccAWSLakeFormationPermissionsDataSource_database(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lakeformation_permissions.test" + dataSourceName := "data.aws_lakeformation_permissions.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck(lakeformation.EndpointsID, t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLakeFormationPermissionsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLakeFormationPermissionsDataSourceConfig_database(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "principal", dataSourceName, "principal"), + resource.TestCheckResourceAttrPair(resourceName, "database.#", dataSourceName, "database.#"), + resource.TestCheckResourceAttrPair(resourceName, "database.0.name", dataSourceName, "database.0.name"), + resource.TestCheckResourceAttrPair(resourceName, "permissions.#", dataSourceName, "permissions.#"), + resource.TestCheckResourceAttrPair(resourceName, "permissions.0", dataSourceName, "permissions.0"), + resource.TestCheckResourceAttrPair(resourceName, "permissions.1", dataSourceName, "permissions.1"), + resource.TestCheckResourceAttrPair(resourceName, "permissions.2", dataSourceName, "permissions.2"), + resource.TestCheckResourceAttrPair(resourceName, "permissions_with_grant_option.#", dataSourceName, "permissions_with_grant_option.#"), + resource.TestCheckResourceAttrPair(resourceName, "permissions_with_grant_option.0", dataSourceName, "permissions_with_grant_option.0"), + ), + }, + }, + }) +} + +func testAccAWSLakeFormationPermissionsDataSource_table(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lakeformation_permissions.test" + dataSourceName := "data.aws_lakeformation_permissions.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck(lakeformation.EndpointsID, t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLakeFormationPermissionsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLakeFormationPermissionsDataSourceConfig_table(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "principal", dataSourceName, "principal"), + resource.TestCheckResourceAttrPair(resourceName, "table.#", dataSourceName, "table.#"), + resource.TestCheckResourceAttrPair(resourceName, "table.0.database_name", dataSourceName, "table.0.database_name"), + resource.TestCheckResourceAttrPair(resourceName, "table.0.name", dataSourceName, "table.0.name"), + resource.TestCheckResourceAttrPair(resourceName, "permissions.#", dataSourceName, "permissions.#"), + resource.TestCheckResourceAttrPair(resourceName, "permissions.0", dataSourceName, "permissions.0"), + ), + }, + }, + }) +} + +func testAccAWSLakeFormationPermissionsDataSource_tableWithColumns(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lakeformation_permissions.test" + dataSourceName := "data.aws_lakeformation_permissions.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck(lakeformation.EndpointsID, t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLakeFormationPermissionsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLakeFormationPermissionsDataSourceConfig_tableWithColumns(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "principal", dataSourceName, "principal"), + resource.TestCheckResourceAttrPair(resourceName, "table_with_columns.#", dataSourceName, "table_with_columns.#"), + resource.TestCheckResourceAttrPair(resourceName, "table_with_columns.0.database_name", dataSourceName, "table_with_columns.0.database_name"), + resource.TestCheckResourceAttrPair(resourceName, "table_with_columns.0.name", dataSourceName, "table_with_columns.0.name"), + resource.TestCheckResourceAttrPair(resourceName, "table_with_columns.0.column_names.#", dataSourceName, "table_with_columns.0.column_names.#"), + resource.TestCheckResourceAttrPair(resourceName, "table_with_columns.0.column_names.0", dataSourceName, "table_with_columns.0.column_names.0"), + resource.TestCheckResourceAttrPair(resourceName, "table_with_columns.0.column_names.1", dataSourceName, "table_with_columns.0.column_names.1"), + resource.TestCheckResourceAttrPair(resourceName, "permissions.#", dataSourceName, "permissions.#"), + resource.TestCheckResourceAttrPair(resourceName, "permissions.0", dataSourceName, "permissions.0"), + ), + }, + }, + }) +} + +func testAccAWSLakeFormationPermissionsDataSourceConfig_basic(rName string) string { + return fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_iam_role" "test" { + name = %[1]q + + assume_role_policy = < 1 { + return fmt.Errorf("error reading Lake Formation permissions: %s", "multiple permissions found") + } + + for _, permissions := range principalResourcePermissions { + d.Set("principal", permissions.Principal.DataLakePrincipalIdentifier) + d.Set("permissions", permissions.Permissions) + d.Set("permissions_with_grant_option", permissions.PermissionsWithGrantOption) + + if permissions.Resource.Catalog != nil { + d.Set("catalog_resource", true) + } + + if permissions.Resource.DataLocation != nil { + d.Set("data_location", []interface{}{flattenLakeFormationDataLocationResource(permissions.Resource.DataLocation)}) + } else { + d.Set("data_location", nil) + } + + if permissions.Resource.Database != nil { + d.Set("database", []interface{}{flattenLakeFormationDatabaseResource(permissions.Resource.Database)}) + } else { + d.Set("database", nil) + } + + // table with columns permissions will include the table and table with columns + if permissions.Resource.TableWithColumns != nil { + d.Set("table_with_columns", []interface{}{flattenLakeFormationTableWithColumnsResource(permissions.Resource.TableWithColumns)}) + } else if permissions.Resource.Table != nil { + d.Set("table_with_columns", nil) + d.Set("table", []interface{}{flattenLakeFormationTableResource(permissions.Resource.Table)}) + } else { + d.Set("table", nil) + } + } + + return nil +} + +func resourceAwsLakeFormationPermissionsDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).lakeformationconn + + input := &lakeformation.RevokePermissionsInput{ + Permissions: expandStringList(d.Get("permissions").([]interface{})), + Principal: &lakeformation.DataLakePrincipal{ + DataLakePrincipalIdentifier: aws.String(d.Get("principal").(string)), + }, + } + + if v, ok := d.GetOk("catalog_id"); ok { + input.CatalogId = aws.String(v.(string)) + } + + if v, ok := d.GetOk("permissions_with_grant_option"); ok { + input.PermissionsWithGrantOption = expandStringList(v.([]interface{})) + } + + input.Resource = expandLakeFormationResource(d, false) + + err := resource.Retry(2*time.Minute, func() *resource.RetryError { + var err error + _, err = conn.RevokePermissions(input) + if err != nil { + if isAWSErr(err, lakeformation.ErrCodeInvalidInputException, "register the S3 path") { + return resource.RetryableError(err) + } + if isAWSErr(err, lakeformation.ErrCodeConcurrentModificationException, "") { + return resource.RetryableError(err) + } + + return resource.NonRetryableError(fmt.Errorf("unable to revoke Lake Formation Permissions: %w", err)) + } + return nil + }) + + if isResourceTimeoutError(err) { + _, err = conn.RevokePermissions(input) + } + + if err != nil { + return fmt.Errorf("unable to revoke LakeFormation Permissions (input: %v): %w", input, err) + } + + return nil +} + +func expandLakeFormationResource(d *schema.ResourceData, squashTableWithColumns bool) *lakeformation.Resource { + res := &lakeformation.Resource{} + + if v, ok := d.GetOk("catalog_resource"); ok { + if v.(bool) { + res.Catalog = &lakeformation.CatalogResource{} + } + } + + if v, ok := d.GetOk("data_location"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + res.DataLocation = expandLakeFormationDataLocationResource(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("database"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + res.Database = expandLakeFormationDatabaseResource(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("table"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + res.Table = expandLakeFormationTableResource(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("table_with_columns"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + if squashTableWithColumns { + // ListPermissions does not support getting privileges by tables with columns. Instead, + // use the table which will return both table and table with columns. + res.Table = expandLakeFormationTableResource(v.([]interface{})[0].(map[string]interface{})) + } else { + res.TableWithColumns = expandLakeFormationTableWithColumnsResource(v.([]interface{})[0].(map[string]interface{})) + } + } + + return res +} + +func expandLakeFormationDataLocationResource(tfMap map[string]interface{}) *lakeformation.DataLocationResource { + if tfMap == nil { + return nil + } + + apiObject := &lakeformation.DataLocationResource{} + + if v, ok := tfMap["catalog_id"].(string); ok && v != "" { + apiObject.CatalogId = aws.String(v) + } + + if v, ok := tfMap["arn"].(string); ok && v != "" { + apiObject.ResourceArn = aws.String(v) + } + + return apiObject +} + +func flattenLakeFormationDataLocationResource(apiObject *lakeformation.DataLocationResource) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.CatalogId; v != nil { + tfMap["catalog_id"] = aws.StringValue(v) + } + + if v := apiObject.ResourceArn; v != nil { + tfMap["arn"] = aws.StringValue(v) + } + + return tfMap +} + +func expandLakeFormationDatabaseResource(tfMap map[string]interface{}) *lakeformation.DatabaseResource { + if tfMap == nil { + return nil + } + + apiObject := &lakeformation.DatabaseResource{} + + if v, ok := tfMap["catalog_id"].(string); ok && v != "" { + apiObject.CatalogId = aws.String(v) + } + + if v, ok := tfMap["name"].(string); ok && v != "" { + apiObject.Name = aws.String(v) + } + + return apiObject +} + +func flattenLakeFormationDatabaseResource(apiObject *lakeformation.DatabaseResource) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.CatalogId; v != nil { + tfMap["catalog_id"] = aws.StringValue(v) + } + + if v := apiObject.Name; v != nil { + tfMap["name"] = aws.StringValue(v) + } + + return tfMap +} + +func expandLakeFormationTableResource(tfMap map[string]interface{}) *lakeformation.TableResource { + if tfMap == nil { + return nil + } + + apiObject := &lakeformation.TableResource{} + + if v, ok := tfMap["catalog_id"].(string); ok && v != "" { + apiObject.CatalogId = aws.String(v) + } + + if v, ok := tfMap["database_name"].(string); ok && v != "" { + apiObject.DatabaseName = aws.String(v) + } + + if v, ok := tfMap["name"].(string); ok && v != "" { + apiObject.Name = aws.String(v) + } + + if v, ok := tfMap["wildcard"].(bool); ok && v { + apiObject.TableWildcard = &lakeformation.TableWildcard{} + } + + return apiObject +} + +func flattenLakeFormationTableResource(apiObject *lakeformation.TableResource) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.CatalogId; v != nil { + tfMap["catalog_id"] = aws.StringValue(v) + } + + if v := apiObject.DatabaseName; v != nil { + tfMap["database_name"] = aws.StringValue(v) + } + + if v := apiObject.Name; v != nil { + tfMap["name"] = aws.StringValue(v) + } + + if v := apiObject.TableWildcard; v != nil { + tfMap["wildcard"] = true + } + + return tfMap +} + +func expandLakeFormationTableWithColumnsResource(tfMap map[string]interface{}) *lakeformation.TableWithColumnsResource { + if tfMap == nil { + return nil + } + + apiObject := &lakeformation.TableWithColumnsResource{} + + if v, ok := tfMap["catalog_id"].(string); ok && v != "" { + apiObject.CatalogId = aws.String(v) + } + + if v, ok := tfMap["column_names"]; ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + apiObject.ColumnNames = expandStringList(v.([]interface{})) + } + + if v, ok := tfMap["database_name"].(string); ok && v != "" { + apiObject.DatabaseName = aws.String(v) + } + + if v, ok := tfMap["excluded_column_names"]; ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + apiObject.ColumnWildcard = &lakeformation.ColumnWildcard{ + ExcludedColumnNames: expandStringList(v.([]interface{})), + } + } + + if v, ok := tfMap["name"].(string); ok && v != "" { + apiObject.Name = aws.String(v) + } + + return apiObject +} + +func flattenLakeFormationTableWithColumnsResource(apiObject *lakeformation.TableWithColumnsResource) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.CatalogId; v != nil { + tfMap["catalog_id"] = aws.StringValue(v) + } + + tfMap["column_names"] = flattenStringList(apiObject.ColumnNames) + + if v := apiObject.DatabaseName; v != nil { + tfMap["database_name"] = aws.StringValue(v) + } + + if v := apiObject.ColumnWildcard; v != nil { + tfMap["excluded_column_names"] = flattenStringList(v.ExcludedColumnNames) + } + + if v := apiObject.Name; v != nil { + tfMap["name"] = aws.StringValue(v) + } + + return tfMap +} diff --git a/aws/resource_aws_lakeformation_permissions_test.go b/aws/resource_aws_lakeformation_permissions_test.go new file mode 100644 index 00000000000..2f96730804d --- /dev/null +++ b/aws/resource_aws_lakeformation_permissions_test.go @@ -0,0 +1,573 @@ +package aws + +import ( + "fmt" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/lakeformation" + "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" +) + +func testAccAWSLakeFormationPermissions_basic(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lakeformation_permissions.test" + roleName := "aws_iam_role.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck(lakeformation.EndpointsID, t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLakeFormationPermissionsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLakeFormationPermissionsConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSLakeFormationPermissionsExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "principal", roleName, "arn"), + resource.TestCheckResourceAttr(resourceName, "permissions.#", "1"), + resource.TestCheckResourceAttr(resourceName, "permissions.0", "CREATE_DATABASE"), + resource.TestCheckResourceAttr(resourceName, "catalog_resource", "true"), + ), + }, + }, + }) +} + +func testAccAWSLakeFormationPermissions_dataLocation(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lakeformation_permissions.test" + roleName := "aws_iam_role.test" + bucketName := "aws_s3_bucket.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck(lakeformation.EndpointsID, t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLakeFormationPermissionsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLakeFormationPermissionsConfig_dataLocation(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSLakeFormationPermissionsExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "principal", roleName, "arn"), + resource.TestCheckResourceAttr(resourceName, "permissions.#", "1"), + resource.TestCheckResourceAttr(resourceName, "permissions.0", "DATA_LOCATION_ACCESS"), + resource.TestCheckResourceAttr(resourceName, "catalog_resource", "false"), + resource.TestCheckResourceAttr(resourceName, "data_location.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "data_location.0.arn", bucketName, "arn"), + ), + }, + }, + }) +} + +func testAccAWSLakeFormationPermissions_database(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lakeformation_permissions.test" + roleName := "aws_iam_role.test" + dbName := "aws_glue_catalog_database.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck(lakeformation.EndpointsID, t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLakeFormationPermissionsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLakeFormationPermissionsConfig_database(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSLakeFormationPermissionsExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "principal", roleName, "arn"), + resource.TestCheckResourceAttr(resourceName, "catalog_resource", "false"), + resource.TestCheckResourceAttrPair(resourceName, "principal", roleName, "arn"), + resource.TestCheckResourceAttr(resourceName, "database.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "database.0.name", dbName, "name"), + resource.TestCheckResourceAttr(resourceName, "permissions.#", "3"), + resource.TestCheckResourceAttr(resourceName, "permissions.0", "ALTER"), + resource.TestCheckResourceAttr(resourceName, "permissions.1", "CREATE_TABLE"), + resource.TestCheckResourceAttr(resourceName, "permissions.2", "DROP"), + resource.TestCheckResourceAttr(resourceName, "permissions_with_grant_option.#", "1"), + resource.TestCheckResourceAttr(resourceName, "permissions_with_grant_option.0", "CREATE_TABLE"), + ), + }, + }, + }) +} + +func testAccAWSLakeFormationPermissions_table(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lakeformation_permissions.test" + roleName := "aws_iam_role.test" + tableName := "aws_glue_catalog_table.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck(lakeformation.EndpointsID, t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLakeFormationPermissionsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLakeFormationPermissionsConfig_table(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSLakeFormationPermissionsExists(resourceName), + resource.TestCheckResourceAttrPair(roleName, "arn", resourceName, "principal"), + resource.TestCheckResourceAttr(resourceName, "table.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "table.0.database_name", tableName, "database_name"), + resource.TestCheckResourceAttrPair(resourceName, "table.0.name", tableName, "name"), + resource.TestCheckResourceAttr(resourceName, "permissions.#", "1"), + resource.TestCheckResourceAttr(resourceName, "permissions.0", "ALL"), + ), + }, + }, + }) +} + +func testAccAWSLakeFormationPermissions_tableWithColumns(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_lakeformation_permissions.test" + roleName := "aws_iam_role.test" + tableName := "aws_glue_catalog_table.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPartitionHasServicePreCheck(lakeformation.EndpointsID, t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLakeFormationPermissionsDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLakeFormationPermissionsConfig_tableWithColumns(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSLakeFormationPermissionsExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "principal", roleName, "arn"), + resource.TestCheckResourceAttr(resourceName, "table_with_columns.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "table_with_columns.0.database_name", tableName, "database_name"), + resource.TestCheckResourceAttrPair(resourceName, "table_with_columns.0.name", tableName, "name"), + resource.TestCheckResourceAttr(resourceName, "table_with_columns.0.column_names.#", "2"), + resource.TestCheckResourceAttr(resourceName, "table_with_columns.0.column_names.0", "event"), + resource.TestCheckResourceAttr(resourceName, "table_with_columns.0.column_names.1", "timestamp"), + resource.TestCheckResourceAttr(resourceName, "permissions.#", "1"), + resource.TestCheckResourceAttr(resourceName, "permissions.0", "SELECT"), + ), + }, + }, + }) +} + +func testAccCheckAWSLakeFormationPermissionsDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).lakeformationconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_lakeformation_permissions" { + continue + } + + principal := rs.Primary.Attributes["principal"] + catalogId := rs.Primary.Attributes["catalog_id"] + + input := &lakeformation.ListPermissionsInput{ + CatalogId: aws.String(catalogId), + Principal: &lakeformation.DataLakePrincipal{ + DataLakePrincipalIdentifier: aws.String(principal), + }, + } + + out, err := conn.ListPermissions(input) + if err == nil { + fmt.Print(out) + return fmt.Errorf("Resource still registered: %s %s", catalogId, principal) + } + } + + return nil +} + +func testAccCheckAWSLakeFormationPermissionsExists(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.ListPermissionsInput{ + MaxResults: aws.Int64(1), + Principal: &lakeformation.DataLakePrincipal{ + DataLakePrincipalIdentifier: aws.String(rs.Primary.Attributes["principal"]), + }, + } + + if v, ok := rs.Primary.Attributes["catalog_resource"]; ok && v != "" && v == "true" { + input.ResourceType = aws.String(lakeformation.DataLakeResourceTypeCatalog) + input.Resource = &lakeformation.Resource{ + Catalog: &lakeformation.CatalogResource{}, + } + } + + if v, ok := rs.Primary.Attributes["data_location.#"]; ok && v != "" && v != "0" { + input.ResourceType = aws.String(lakeformation.DataLakeResourceTypeDataLocation) + res := &lakeformation.DataLocationResource{ + ResourceArn: aws.String(rs.Primary.Attributes["data_location.0.arn"]), + } + if rs.Primary.Attributes["data_location.0.catalog_id"] != "" { + res.CatalogId = aws.String(rs.Primary.Attributes["data_location.0.catalog_id"]) + } + input.Resource = &lakeformation.Resource{ + DataLocation: res, + } + } + + if v, ok := rs.Primary.Attributes["database.#"]; ok && v != "" && v != "0" { + input.ResourceType = aws.String(lakeformation.DataLakeResourceTypeDatabase) + res := &lakeformation.DatabaseResource{ + Name: aws.String(rs.Primary.Attributes["database.0.name"]), + } + if rs.Primary.Attributes["database.0.catalog_id"] != "" { + res.CatalogId = aws.String(rs.Primary.Attributes["database.0.catalog_id"]) + } + input.Resource = &lakeformation.Resource{ + Database: res, + } + } + + if v, ok := rs.Primary.Attributes["table.#"]; ok && v != "" && v != "0" { + input.ResourceType = aws.String(lakeformation.DataLakeResourceTypeTable) + res := &lakeformation.TableResource{ + DatabaseName: aws.String(rs.Primary.Attributes["table.0.database_name"]), + } + if rs.Primary.Attributes["table.0.catalog_id"] != "" { + res.CatalogId = aws.String(rs.Primary.Attributes["table.0.catalog_id"]) + } + if rs.Primary.Attributes["table.0.name"] != "" { + res.Name = aws.String(rs.Primary.Attributes["table.0.name"]) + } + if rs.Primary.Attributes["table.0.wildcard"] == "true" { + res.TableWildcard = &lakeformation.TableWildcard{} + } + input.Resource = &lakeformation.Resource{ + Table: res, + } + } + + // ListPermissions does not support getting privileges on a table with columns. + // Instead, call this operation on the table, and the operation returns the + // table and the table w columns. + // https://docs.aws.amazon.com/sdk-for-go/api/service/lakeformation/#ListPermissionsInput + if v, ok := rs.Primary.Attributes["table_with_columns.#"]; ok && v != "" && v != "0" { + input.ResourceType = aws.String(lakeformation.DataLakeResourceTypeTable) + res := &lakeformation.TableResource{ + DatabaseName: aws.String(rs.Primary.Attributes["table_with_columns.0.database_name"]), + Name: aws.String(rs.Primary.Attributes["table_with_columns.0.name"]), + } + if rs.Primary.Attributes["table.0.catalog_id"] != "" { + res.CatalogId = aws.String(rs.Primary.Attributes["table.0.catalog_id"]) + } + input.Resource = &lakeformation.Resource{ + Table: res, + } + } + + err := resource.Retry(2*time.Minute, func() *resource.RetryError { + var err error + _, err = conn.ListPermissions(input) + if err != nil { + if isAWSErr(err, lakeformation.ErrCodeInvalidInputException, "Invalid principal") { + return resource.RetryableError(err) + } + if isAWSErr(err, lakeformation.ErrCodeInvalidInputException, "Grantee has no permissions") { + return resource.RetryableError(err) + } + if isAWSErr(err, lakeformation.ErrCodeInvalidInputException, "register the S3 path") { + return resource.RetryableError(err) + } + if isAWSErr(err, lakeformation.ErrCodeConcurrentModificationException, "") { + return resource.RetryableError(err) + } + if isAWSErr(err, "AccessDeniedException", "is not authorized to access requested permissions") { + return resource.RetryableError(err) + } + + return resource.NonRetryableError(fmt.Errorf("unable to get Lake Formation Permissions: %w", err)) + } + return nil + }) + + if isResourceTimeoutError(err) { + _, err = conn.ListPermissions(input) + } + + if err != nil { + return fmt.Errorf("unable to get Lake Formation permissions (%s): %w", rs.Primary.ID, err) + } + + return nil + } +} + +func testAccAWSLakeFormationPermissionsConfig_basic(rName string) string { + return fmt.Sprintf(` +data "aws_partition" "current" {} + +resource "aws_iam_role" "test" { + name = %[1]q + + assume_role_policy = < **NOTE:** This data source deals with explicitly granted permissions. Lake Formation grants implicit permissions to data lake administrators, database creators, and table creators. For more information, see [Implicit Lake Formation Permissions](https://docs.aws.amazon.com/lake-formation/latest/dg/implicit-permissions.html). + +## Example Usage + +### Permissions For A Lake Formation S3 Resource + +```hcl +data "aws_lakeformation_permissions" "test" { + principal = aws_iam_role.workflow_role.arn + + data_location { + arn = aws_lakeformation_resource.test.arn + } +} +``` + +### Permissions For A Glue Catalog Database + +```hcl +data "aws_lakeformation_permissions" "test" { + principal = aws_iam_role.workflow_role.arn + + database { + name = aws_glue_catalog_database.test.name + catalog_id = "110376042874" + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `principal` – (Required) Principal to be granted the permissions on the resource. Supported principals are IAM users or IAM roles. + +One of the following is required: + +* `catalog_resource` - Whether the permissions are to be granted for the Data Catalog. Defaults to `false`. +* `data_location` - Configuration block for a data location resource. Detailed below. +* `database` - Configuration block for a database resource. Detailed below. +* `table` - Configuration block for a table resource. Detailed below. +* `table_with_columns` - Configuration block for a table with columns resource. Detailed below. + +The following arguments are optional: + +* `catalog_id` – (Optional) Identifier for the Data Catalog. By default, the account ID. The Data Catalog is the persistent metadata store. It contains database definitions, table definitions, and other control information to manage your Lake Formation environment. + +### data_location + +The following argument is required: + +* `arn` – (Required) Amazon Resource Name (ARN) that uniquely identifies the data location resource. + +The following argument is optional: + +* `catalog_id` - (Optional) Identifier for the Data Catalog where the location is registered with Lake Formation. By default, it is the account ID of the caller. + +### database + +The following argument is required: + +* `name` – (Required) Name of the database resource. Unique to the Data Catalog. + +The following argument is optional: + +* `catalog_id` - (Optional) Identifier for the Data Catalog. By default, it is the account ID of the caller. + +### table + +The following argument is required: + +* `database_name` – (Required) Name of the database for the table. Unique to a Data Catalog. + +The following arguments are optional: + +* `catalog_id` - (Optional) Identifier for the Data Catalog. By default, it is the account ID of the caller. +* `name` - (Optional) Name of the table. At least one of `name` or `wildcard` is required. +* `wildcard` - (Optional) Whether to use a wildcard representing every table under a database. At least one of `name` or `wildcard` is required. Defaults to `false`. + +### table_with_columns + +The following arguments are required: + +* `database_name` – (Required) Name of the database for the table with columns resource. Unique to the Data Catalog. +* `name` – (Required) Name of the table resource. + +The following arguments are optional: + +* `catalog_id` - (Optional) Identifier for the Data Catalog. By default, it is the account ID of the caller. +* `column_names` - (Optional) List of column names for the table. At least one of `column_names` or `excluded_column_names` is required. +* `excluded_column_names` - (Optional) List of column names for the table to exclude. At least one of `column_names` or `excluded_column_names` is required. + +## Attributes Reference + +In addition to the above arguments, the following attribute is exported: + +* `permissions` – List of permissions granted to the principal. For details on permissions, see [Lake Formation Permissions Reference](https://docs.aws.amazon.com/lake-formation/latest/dg/lf-permissions-reference.html). +* `permissions_with_grant_option` - Subset of `permissions` which the principal can pass. diff --git a/website/docs/d/lakeformation_resource.html.markdown b/website/docs/d/lakeformation_resource.html.markdown new file mode 100644 index 00000000000..70285dc5019 --- /dev/null +++ b/website/docs/d/lakeformation_resource.html.markdown @@ -0,0 +1,30 @@ +--- +subcategory: "Lake Formation" +layout: "aws" +page_title: "AWS: aws_lakeformation_resource" +description: |- + Provides details about a Lake Formation resource. +--- + +# Data Source: aws_lakeformation_resource + +Provides details about a Lake Formation resource. + +## Example Usage + +```hcl +data "aws_lakeformation_resource" "example" { + arn = "arn:aws:s3:::tf-acc-test-9151654063908211878" +} +``` + +## Argument Reference + +* `arn` – (Required) Amazon Resource Name (ARN) of the resource, an S3 path. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `last_modified` - The date and time the resource was last modified in [RFC 3339 format](https://tools.ietf.org/html/rfc3339#section-5.8). +* `role_arn` – Role that the resource was registered with. diff --git a/website/docs/r/lakeformation_data_lake_settings.html.markdown b/website/docs/r/lakeformation_data_lake_settings.html.markdown index 6da510981c7..0b4123bc10e 100644 --- a/website/docs/r/lakeformation_data_lake_settings.html.markdown +++ b/website/docs/r/lakeformation_data_lake_settings.html.markdown @@ -18,7 +18,7 @@ Manages Lake Formation principals designated as data lake administrators and lis ```hcl resource "aws_lakeformation_data_lake_settings" "example" { - data_lake_admins = [aws_iam_user.test.arn, aws_iam_role.test.arn] + admins = [aws_iam_user.test.arn, aws_iam_role.test.arn] } ``` @@ -26,7 +26,7 @@ resource "aws_lakeformation_data_lake_settings" "example" { ```hcl resource "aws_lakeformation_data_lake_settings" "example" { - data_lake_admins = [aws_iam_user.test.arn, aws_iam_role.test.arn] + admins = [aws_iam_user.test.arn, aws_iam_role.test.arn] create_database_default_permissions { permissions = ["SELECT", "ALTER", "DROP"] @@ -44,24 +44,26 @@ resource "aws_lakeformation_data_lake_settings" "example" { The following arguments are optional: +* `admins` – (Optional) List of ARNs of AWS Lake Formation principals (IAM users or roles). * `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). +~> **NOTE:** Although optional, not including `admins`, `create_database_default_permissions`, `create_table_default_permissions`, and/or `trusted_resource_owners` results in the setting being cleared. + ### 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`. +* `permissions` - (Optional) List of permissions that are granted to the principal. Valid values may include `ALL`, `SELECT`, `ALTER`, `DROP`, `DELETE`, `INSERT`, `DESCRIBE`, and `CREATE_TABLE`. For more details, see [Lake Formation Permissions Reference](https://docs.aws.amazon.com/lake-formation/latest/dg/lf-permissions-reference.html). * `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`. +* `permissions` - (Optional) List of permissions that are granted to the principal. Valid values may include `ALL`, `SELECT`, `ALTER`, `DROP`, `DELETE`, `INSERT`, and `DESCRIBE`. For more details, see [Lake Formation Permissions Reference](https://docs.aws.amazon.com/lake-formation/latest/dg/lf-permissions-reference.html). * `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 diff --git a/website/docs/r/lakeformation_permissions.html.markdown b/website/docs/r/lakeformation_permissions.html.markdown new file mode 100644 index 00000000000..610761119f5 --- /dev/null +++ b/website/docs/r/lakeformation_permissions.html.markdown @@ -0,0 +1,117 @@ +--- +subcategory: "Lake Formation" +layout: "aws" +page_title: "AWS: aws_lakeformation_permissions" +description: |- + Grants permissions to the principal to access metadata in the Data Catalog and data organized in underlying data storage such as Amazon S3. +--- + +# Resource: aws_lakeformation_permissions + +Grants permissions to the principal to access metadata in the Data Catalog and data organized in underlying data storage such as Amazon S3. Permissions are granted to a principal, in a Data Catalog, relative to a Lake Formation resource, which includes the Data Catalog, databases, and tables. For more information, see [Security and Access Control to Metadata and Data in Lake Formation](https://docs.aws.amazon.com/lake-formation/latest/dg/security-data-access.html). + +~> **NOTE:** This resource deals with explicitly granted permissions. Lake Formation grants implicit permissions to data lake administrators, database creators, and table creators. For more information, see [Implicit Lake Formation Permissions](https://docs.aws.amazon.com/lake-formation/latest/dg/implicit-permissions.html). + +## Example Usage + +### Grant Permissions For A Lake Formation S3 Resource + +```hcl +resource "aws_lakeformation_permissions" "test" { + principal = aws_iam_role.workflow_role.arn + permissions = ["ALL"] + + data_location { + arn = aws_lakeformation_resource.test.arn + } +} +``` + +### Grant Permissions For A Glue Catalog Database + +```hcl +resource "aws_lakeformation_permissions" "test" { + role = aws_iam_role.workflow_role.arn + permissions = ["CREATE_TABLE", "ALTER", "DROP"] + + database { + name = aws_glue_catalog_database.test.name + catalog_id = "110376042874" + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `permissions` – (Required) List of permissions granted to the principal. Valid values may include `ALL`, `ALTER`, `CREATE_DATABASE`, `CREATE_TABLE`, `DATA_LOCATION_ACCESS`, `DELETE`, `DESCRIBE`, `DROP`, `INSERT`, and `SELECT`. For details on each permission, see [Lake Formation Permissions Reference](https://docs.aws.amazon.com/lake-formation/latest/dg/lf-permissions-reference.html). +* `principal` – (Required) Principal to be granted the permissions on the resource. Supported principals include IAM users and IAM roles. + +One of the following is required: + +* `catalog_resource` - (Optional) Whether the permissions are to be granted for the Data Catalog. Defaults to `false`. +* `data_location` - (Optional) Configuration block for a data location resource. Detailed below. +* `database` - (Optional) Configuration block for a database resource. Detailed below. +* `table` - (Optional) Configuration block for a table resource. Detailed below. +* `table_with_columns` - (Optional) Configuration block for a table with columns resource. Detailed below. + +The following arguments are optional: + +* `catalog_id` – (Optional) Identifier for the Data Catalog. By default, the account ID. The Data Catalog is the persistent metadata store. It contains database definitions, table definitions, and other control information to manage your Lake Formation environment. +* `permissions_with_grant_option` - (Optional) Subset of `permissions` which the principal can pass. + +### data_location + +The following argument is required: + +* `arn` – (Required) Amazon Resource Name (ARN) that uniquely identifies the data location resource. + +The following argument is optional: + +* `catalog_id` - (Optional) Identifier for the Data Catalog where the location is registered with Lake Formation. By default, it is the account ID of the caller. + +### database + +The following argument is required: + +* `name` – (Required) Name of the database resource. Unique to the Data Catalog. + +The following argument is optional: + +* `catalog_id` - (Optional) Identifier for the Data Catalog. By default, it is the account ID of the caller. + +### table + +The following argument is required: + +* `database_name` – (Required) Name of the database for the table. Unique to a Data Catalog. + +At least one of the following is required: + +* `name` - (Optional) Name of the table. +* `wildcard` - (Optional) Whether to use a wildcard representing every table under a database. Defaults to `false`. + +The following arguments are optional: + +* `catalog_id` - (Optional) Identifier for the Data Catalog. By default, it is the account ID of the caller. + +### table_with_columns + +The following arguments are required: + +* `database_name` – (Required) Name of the database for the table with columns resource. Unique to the Data Catalog. +* `name` – (Required) Name of the table resource. + +At least one of the following is required: + +* `column_names` - (Optional) List of column names for the table. +* `excluded_column_names` - (Optional) List of column names for the table to exclude. + +The following arguments are optional: + +* `catalog_id` - (Optional) Identifier for the Data Catalog. By default, it is the account ID of the caller. + +## Attributes Reference + +In addition to the above arguments, no attributes are exported. diff --git a/website/docs/r/lakeformation_resource.html.markdown b/website/docs/r/lakeformation_resource.html.markdown index 6444cbe713f..27eb12fc6dc 100644 --- a/website/docs/r/lakeformation_resource.html.markdown +++ b/website/docs/r/lakeformation_resource.html.markdown @@ -20,15 +20,13 @@ data "aws_s3_bucket" "example" { } resource "aws_lakeformation_resource" "example" { - resource_arn = data.aws_s3_bucket.example.arn + arn = data.aws_s3_bucket.example.arn } ``` ## Argument Reference -The following arguments are required: - -* `resource_arn` – (Required) Amazon Resource Name (ARN) of the resource, an S3 path. +* `arn` – (Required) Amazon Resource Name (ARN) of the resource, an S3 path. * `role_arn` – (Optional) Role that has read/write access to the resource. If not provided, the Lake Formation service-linked role must exist and is used. ~> **NOTE:** AWS does not support registering an S3 location with an IAM role and subsequently updating the S3 location registration to a service-linked role.