Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lake Formation Permissions #13396

Merged
merged 31 commits into from
Dec 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
431f32b
New resource: aws_lakeformation_datalake_settings
gmazelier May 9, 2020
3dcb295
Add resource documentation
gmazelier May 10, 2020
ed879d5
New resource: aws_lakeformation_permissions (WIP)
gmazelier May 10, 2020
01999bd
Add documentation
gmazelier May 19, 2020
d85171b
Define permissions at catalog level
gmazelier May 20, 2020
d4845eb
Test multiple permissions on table resource
gmazelier May 20, 2020
fd0adb6
Check that permissions are revoked in acceptance tests
gmazelier May 20, 2020
419d805
Add timestamp to resource ID
gmazelier May 20, 2020
12bc694
Use const defined in API for permissions
gmazelier May 20, 2020
8858863
Cleanup
gmazelier May 20, 2020
661e2b3
Handle TableWithColumns resource I/O in ListPermissions request
gmazelier May 26, 2020
708c760
Fix type in HCL example
gmazelier May 26, 2020
c5fd916
Remove redundant check, add one test step
gmazelier May 27, 2020
2d4cb28
resource/lakeformation: Remove aws.erb from PR
YakDriver Dec 11, 2020
7411370
resource/lakeformation_permissions: Upgrade to plugin SDK v2
YakDriver Dec 11, 2020
eeac9a3
resource/lakeformation_permissions: Remove datalake_settings from PR
YakDriver Dec 11, 2020
1f2e587
resource/lakeformation_permissions: Remove PR-extraneous files
YakDriver Dec 11, 2020
94b4f58
ds/lakeformation_resource: New data source
YakDriver Dec 11, 2020
1a12e1a
resource/lakeformation_resource: Improve docs, error messages (minor)
YakDriver Dec 11, 2020
5db1eb9
docs/resource/lakeformation_permissions: Rework for design
YakDriver Dec 11, 2020
60b7aef
resource/lakeformation_permissions: Align with design and conventions
YakDriver Dec 15, 2020
e139a59
validators: Add ValidatePrincipal for Lake Formation principals
YakDriver Dec 17, 2020
cb15321
resource/lakeformation: Use ValidatePrincipal for Lake Formation prin…
YakDriver Dec 17, 2020
cd9e6c2
resource/lakeformation_permissions: Eventual consistency issues
YakDriver Dec 17, 2020
544bff9
resource/lakeformation_permissions: Adjust for passing tests
YakDriver Dec 17, 2020
e873f56
resource/lakeformation: Pre-merge cleanup
YakDriver Dec 18, 2020
641fbd4
resource/lakeformation: Fix linting
YakDriver Dec 18, 2020
e170ade
ds/lakeformation_permissions: New data source
YakDriver Dec 18, 2020
70e0cf1
resource/lakeformation: Fix linter issues
YakDriver Dec 18, 2020
3ef409d
resource/lakeformation: Fix semgrep issues
YakDriver Dec 18, 2020
7ca0739
Update CHANGELOG with Lake Formation
YakDriver Dec 18, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions aws/data_source_aws_lakeformation_data_lake_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
22 changes: 4 additions & 18 deletions aws/data_source_aws_lakeformation_data_lake_settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"),
),
},
},
Expand All @@ -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" {
Expand Down
266 changes: 266 additions & 0 deletions aws/data_source_aws_lakeformation_permissions.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading