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

r/aws_glue_catalog_database: add federated_database argument #35799

Merged
merged 3 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions .changelog/35799.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
```release-note:bug
resource/aws_lakeformation_resource: Properly handle configured `false` values for `use_service_linked_role`
```
```release-note:enhancement
resource/aws_glue_catalog_database: Add `federated_database` argument
```
69 changes: 69 additions & 0 deletions internal/service/glue/catalog_database.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,23 @@ func ResourceCatalogDatabase() *schema.Resource {
Optional: true,
ValidateFunc: validation.StringLenBetween(0, 2048),
},
"federated_database": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"connection_name": {
Type: schema.TypeString,
Optional: true,
},
"identifier": {
Type: schema.TypeString,
Optional: true,
},
},
},
},
"location_uri": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -155,6 +172,10 @@ func resourceCatalogDatabaseCreate(ctx context.Context, d *schema.ResourceData,
dbInput.Parameters = flex.ExpandStringMap(v.(map[string]interface{}))
}

if v, ok := d.GetOk("federated_database"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
dbInput.FederatedDatabase = expandDatabaseFederatedDatabase(v.([]interface{})[0].(map[string]interface{}))
}

if v, ok := d.GetOk("target_database"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
dbInput.TargetDatabase = expandDatabaseTargetDatabase(v.([]interface{})[0].(map[string]interface{}))
}
Expand Down Expand Up @@ -210,6 +231,10 @@ func resourceCatalogDatabaseUpdate(ctx context.Context, d *schema.ResourceData,
dbInput.Parameters = flex.ExpandStringMap(v.(map[string]interface{}))
}

if v, ok := d.GetOk("federated_database"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
dbInput.FederatedDatabase = expandDatabaseFederatedDatabase(v.([]interface{})[0].(map[string]interface{}))
}

if v, ok := d.GetOk("create_table_default_permission"); ok && len(v.([]interface{})) > 0 {
dbInput.CreateTableDefaultPermissions = expandDatabasePrincipalPermissions(v.([]interface{}))
}
Expand Down Expand Up @@ -259,6 +284,14 @@ func resourceCatalogDatabaseRead(ctx context.Context, d *schema.ResourceData, me
d.Set("location_uri", database.LocationUri)
d.Set("parameters", aws.StringValueMap(database.Parameters))

if database.FederatedDatabase != nil {
if err := d.Set("federated_database", []interface{}{flattenDatabaseFederatedDatabase(database.FederatedDatabase)}); err != nil {
return sdkdiag.AppendErrorf(diags, "setting federated_database: %s", err)
}
} else {
d.Set("federated_database", nil)
}

if database.TargetDatabase != nil {
if err := d.Set("target_database", []interface{}{flattenDatabaseTargetDatabase(database.TargetDatabase)}); err != nil {
return sdkdiag.AppendErrorf(diags, "setting target_database: %s", err)
Expand Down Expand Up @@ -310,6 +343,24 @@ func createCatalogID(d *schema.ResourceData, accountid string) (catalogID string
return
}

func expandDatabaseFederatedDatabase(tfMap map[string]interface{}) *glue.FederatedDatabase {
if tfMap == nil {
return nil
}

apiObject := &glue.FederatedDatabase{}

if v, ok := tfMap["connection_name"].(string); ok && v != "" {
apiObject.ConnectionName = aws.String(v)
}

if v, ok := tfMap["identifier"].(string); ok && v != "" {
apiObject.Identifier = aws.String(v)
}

return apiObject
}

func expandDatabaseTargetDatabase(tfMap map[string]interface{}) *glue.DatabaseIdentifier {
if tfMap == nil {
return nil
Expand All @@ -332,6 +383,24 @@ func expandDatabaseTargetDatabase(tfMap map[string]interface{}) *glue.DatabaseId
return apiObject
}

func flattenDatabaseFederatedDatabase(apiObject *glue.FederatedDatabase) map[string]interface{} {
if apiObject == nil {
return nil
}

tfMap := map[string]interface{}{}

if v := apiObject.ConnectionName; v != nil {
tfMap["connection_name"] = aws.StringValue(v)
}

if v := apiObject.Identifier; v != nil {
tfMap["identifier"] = aws.StringValue(v)
}

return tfMap
}

func flattenDatabaseTargetDatabase(apiObject *glue.DatabaseIdentifier) map[string]interface{} {
if apiObject == nil {
return nil
Expand Down
117 changes: 117 additions & 0 deletions internal/service/glue/catalog_database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"testing"

"github.com/YakDriver/regexache"
"github.com/aws/aws-sdk-go/service/glue"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
Expand Down Expand Up @@ -194,6 +195,38 @@ func TestAccGlueCatalogDatabase_targetDatabaseWithRegion(t *testing.T) {
})
}

func TestAccGlueCatalogDatabase_federatedDatabase(t *testing.T) {
ctx := acctest.Context(t)
resourceName := "aws_glue_catalog_database.test"
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, glue.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckDatabaseDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccCatalogDatabaseConfig_federatedDatabase(rName),
Destroy: false,
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckCatalogDatabaseExists(ctx, resourceName),
resource.TestCheckResourceAttr(resourceName, "name", rName),
acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "glue", fmt.Sprintf("database/%s", rName)),
resource.TestCheckResourceAttr(resourceName, "federated_database.#", "1"),
resource.TestCheckResourceAttr(resourceName, "federated_database.0.connection_name", "aws:redshift"),
acctest.MatchResourceAttrRegionalARN(resourceName, "federated_database.0.identifier", "redshift", regexache.MustCompile(`datashare:+.`)),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccGlueCatalogDatabase_tags(t *testing.T) {
ctx := acctest.Context(t)
resourceName := "aws_glue_catalog_database.test"
Expand Down Expand Up @@ -320,6 +353,90 @@ resource "aws_glue_catalog_database" "test" {
`, rName, desc)
}

func testAccCatalogDatabaseConfig_federatedDatabase(rName string) string {
return acctest.ConfigCompose(
fmt.Sprintf(`
data "aws_region" "current" {}
data "aws_partition" "current" {}
data "aws_caller_identity" "current" {}

resource "aws_redshiftserverless_namespace" "test" {
namespace_name = %[1]q
db_name = "test"
}

resource "aws_redshiftserverless_workgroup" "test" {
namespace_name = aws_redshiftserverless_namespace.test.namespace_name
workgroup_name = %[1]q
}

resource "aws_redshiftdata_statement" "test_create" {
workgroup_name = aws_redshiftserverless_workgroup.test.workgroup_name
database = aws_redshiftserverless_namespace.test.db_name
sql = "CREATE DATASHARE tfacctest;"
}
`, rName),
// Split this resource into a string literal so the terraform `format` function
// interpolates properly
`
resource "aws_redshiftdata_statement" "test_grant_usage" {
depends_on = [aws_redshiftdata_statement.test_create]
workgroup_name = aws_redshiftserverless_workgroup.test.workgroup_name
database = aws_redshiftserverless_namespace.test.db_name
sql = format("GRANT USAGE ON DATASHARE tfacctest TO ACCOUNT '%s' VIA DATA CATALOG;", data.aws_caller_identity.current.account_id)
}

locals {
# Data share ARN is not returned from the GRANT USAGE statement, so must be
# composed manually.
# Ref: https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonredshift.html#amazonredshift-resources-for-iam-policies
data_share_arn = format("arn:%s:redshift:%s:%s:datashare:%s/%s",
data.aws_partition.current.id,
data.aws_region.current.name,
data.aws_caller_identity.current.account_id,
aws_redshiftserverless_namespace.test.namespace_id,
"tfacctest",
)
}

resource "aws_redshift_data_share_authorization" "test" {
depends_on = [aws_redshiftdata_statement.test_grant_usage]

data_share_arn = local.data_share_arn
consumer_identifier = format("DataCatalog/%s", data.aws_caller_identity.current.account_id)
}

resource "aws_redshift_data_share_consumer_association" "test" {
depends_on = [aws_redshift_data_share_authorization.test]

data_share_arn = local.data_share_arn
consumer_arn = format("arn:%s:glue:%s:%s:catalog",
data.aws_partition.current.id,
data.aws_region.current.name,
data.aws_caller_identity.current.account_id,
)
}

resource "aws_lakeformation_resource" "test" {
depends_on = [aws_redshift_data_share_consumer_association.test]

arn = local.data_share_arn
use_service_linked_role = false
}
`,
fmt.Sprintf(`
resource "aws_glue_catalog_database" "test" {
depends_on = [aws_lakeformation_resource.test]

name = %[1]q
federated_database {
connection_name = "aws:redshift"
identifier = local.data_share_arn
}
}
`, rName))
}

func testAccCatalogDatabaseConfig_target(rName string) string {
return fmt.Sprintf(`
resource "aws_glue_catalog_database" "test" {
Expand Down
2 changes: 1 addition & 1 deletion internal/service/lakeformation/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func resourceResourceCreate(ctx context.Context, d *schema.ResourceData, meta in
input.UseServiceLinkedRole = aws.Bool(true)
}

if v, ok := d.GetOk("use_service_linked_role"); ok {
if v, ok := d.GetOkExists("use_service_linked_role"); ok {
input.UseServiceLinkedRole = aws.Bool(v.(bool))
}

Expand Down
10 changes: 8 additions & 2 deletions website/docs/r/glue_catalog_database.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ Provides a Glue Catalog Database Resource. You can refer to the [Glue Developer
## Example Usage

```terraform
resource "aws_glue_catalog_database" "aws_glue_catalog_database" {
resource "aws_glue_catalog_database" "example" {
name = "MyCatalogDatabase"
}
```

### Create Table Default Permissions

```terraform
resource "aws_glue_catalog_database" "aws_glue_catalog_database" {
resource "aws_glue_catalog_database" "example" {
name = "MyCatalogDatabase"

create_table_default_permission {
Expand All @@ -41,12 +41,18 @@ This resource supports the following arguments:
* `catalog_id` - (Optional) ID of the Glue Catalog to create the database in. If omitted, this defaults to the AWS Account ID.
* `create_table_default_permission` - (Optional) Creates a set of default permissions on the table for principals. See [`create_table_default_permission`](#create_table_default_permission) below.
* `description` - (Optional) Description of the database.
* `federated_database` - (Optional) Configuration block that references an entity outside the AWS Glue Data Catalog. See [`federated_database`](#federated_database) below.
* `location_uri` - (Optional) Location of the database (for example, an HDFS path).
* `name` - (Required) Name of the database. The acceptable characters are lowercase letters, numbers, and the underscore character.
* `parameters` - (Optional) List of key-value pairs that define parameters and properties of the database.
* `tags` - (Optional) Key-value map of resource tags. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level.
* `target_database` - (Optional) Configuration block for a target database for resource linking. See [`target_database`](#target_database) below.

### federated_database

* `connection_name` - (Optional) Name of the connection to the external metastore.
* `identifier` - (Optional) Unique identifier for the federated database.

### target_database

* `catalog_id` - (Required) ID of the Data Catalog in which the database resides.
Expand Down
Loading