Skip to content

Commit f03c92e

Browse files
authored
Merge pull request #35799 from hashicorp/f-glue-federated-database
r/aws_glue_catalog_database: add `federated_database` argument
2 parents 3e554cc + e65df6b commit f03c92e

File tree

5 files changed

+201
-3
lines changed

5 files changed

+201
-3
lines changed

.changelog/35799.txt

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
```release-note:bug
2+
resource/aws_lakeformation_resource: Properly handle configured `false` values for `use_service_linked_role`
3+
```
4+
```release-note:enhancement
5+
resource/aws_glue_catalog_database: Add `federated_database` argument
6+
```

internal/service/glue/catalog_database.go

+69
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,23 @@ func ResourceCatalogDatabase() *schema.Resource {
9595
Optional: true,
9696
ValidateFunc: validation.StringLenBetween(0, 2048),
9797
},
98+
"federated_database": {
99+
Type: schema.TypeList,
100+
Optional: true,
101+
MaxItems: 1,
102+
Elem: &schema.Resource{
103+
Schema: map[string]*schema.Schema{
104+
"connection_name": {
105+
Type: schema.TypeString,
106+
Optional: true,
107+
},
108+
"identifier": {
109+
Type: schema.TypeString,
110+
Optional: true,
111+
},
112+
},
113+
},
114+
},
98115
"location_uri": {
99116
Type: schema.TypeString,
100117
Optional: true,
@@ -155,6 +172,10 @@ func resourceCatalogDatabaseCreate(ctx context.Context, d *schema.ResourceData,
155172
dbInput.Parameters = flex.ExpandStringMap(v.(map[string]interface{}))
156173
}
157174

175+
if v, ok := d.GetOk("federated_database"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
176+
dbInput.FederatedDatabase = expandDatabaseFederatedDatabase(v.([]interface{})[0].(map[string]interface{}))
177+
}
178+
158179
if v, ok := d.GetOk("target_database"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
159180
dbInput.TargetDatabase = expandDatabaseTargetDatabase(v.([]interface{})[0].(map[string]interface{}))
160181
}
@@ -210,6 +231,10 @@ func resourceCatalogDatabaseUpdate(ctx context.Context, d *schema.ResourceData,
210231
dbInput.Parameters = flex.ExpandStringMap(v.(map[string]interface{}))
211232
}
212233

234+
if v, ok := d.GetOk("federated_database"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
235+
dbInput.FederatedDatabase = expandDatabaseFederatedDatabase(v.([]interface{})[0].(map[string]interface{}))
236+
}
237+
213238
if v, ok := d.GetOk("create_table_default_permission"); ok && len(v.([]interface{})) > 0 {
214239
dbInput.CreateTableDefaultPermissions = expandDatabasePrincipalPermissions(v.([]interface{}))
215240
}
@@ -259,6 +284,14 @@ func resourceCatalogDatabaseRead(ctx context.Context, d *schema.ResourceData, me
259284
d.Set("location_uri", database.LocationUri)
260285
d.Set("parameters", aws.StringValueMap(database.Parameters))
261286

287+
if database.FederatedDatabase != nil {
288+
if err := d.Set("federated_database", []interface{}{flattenDatabaseFederatedDatabase(database.FederatedDatabase)}); err != nil {
289+
return sdkdiag.AppendErrorf(diags, "setting federated_database: %s", err)
290+
}
291+
} else {
292+
d.Set("federated_database", nil)
293+
}
294+
262295
if database.TargetDatabase != nil {
263296
if err := d.Set("target_database", []interface{}{flattenDatabaseTargetDatabase(database.TargetDatabase)}); err != nil {
264297
return sdkdiag.AppendErrorf(diags, "setting target_database: %s", err)
@@ -310,6 +343,24 @@ func createCatalogID(d *schema.ResourceData, accountid string) (catalogID string
310343
return
311344
}
312345

346+
func expandDatabaseFederatedDatabase(tfMap map[string]interface{}) *glue.FederatedDatabase {
347+
if tfMap == nil {
348+
return nil
349+
}
350+
351+
apiObject := &glue.FederatedDatabase{}
352+
353+
if v, ok := tfMap["connection_name"].(string); ok && v != "" {
354+
apiObject.ConnectionName = aws.String(v)
355+
}
356+
357+
if v, ok := tfMap["identifier"].(string); ok && v != "" {
358+
apiObject.Identifier = aws.String(v)
359+
}
360+
361+
return apiObject
362+
}
363+
313364
func expandDatabaseTargetDatabase(tfMap map[string]interface{}) *glue.DatabaseIdentifier {
314365
if tfMap == nil {
315366
return nil
@@ -332,6 +383,24 @@ func expandDatabaseTargetDatabase(tfMap map[string]interface{}) *glue.DatabaseId
332383
return apiObject
333384
}
334385

386+
func flattenDatabaseFederatedDatabase(apiObject *glue.FederatedDatabase) map[string]interface{} {
387+
if apiObject == nil {
388+
return nil
389+
}
390+
391+
tfMap := map[string]interface{}{}
392+
393+
if v := apiObject.ConnectionName; v != nil {
394+
tfMap["connection_name"] = aws.StringValue(v)
395+
}
396+
397+
if v := apiObject.Identifier; v != nil {
398+
tfMap["identifier"] = aws.StringValue(v)
399+
}
400+
401+
return tfMap
402+
}
403+
335404
func flattenDatabaseTargetDatabase(apiObject *glue.DatabaseIdentifier) map[string]interface{} {
336405
if apiObject == nil {
337406
return nil

internal/service/glue/catalog_database_test.go

+117
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"testing"
1010

11+
"github.com/YakDriver/regexache"
1112
"github.com/aws/aws-sdk-go/service/glue"
1213
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1314
sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
@@ -194,6 +195,38 @@ func TestAccGlueCatalogDatabase_targetDatabaseWithRegion(t *testing.T) {
194195
})
195196
}
196197

198+
func TestAccGlueCatalogDatabase_federatedDatabase(t *testing.T) {
199+
ctx := acctest.Context(t)
200+
resourceName := "aws_glue_catalog_database.test"
201+
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
202+
203+
resource.ParallelTest(t, resource.TestCase{
204+
PreCheck: func() { acctest.PreCheck(ctx, t) },
205+
ErrorCheck: acctest.ErrorCheck(t, glue.EndpointsID),
206+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
207+
CheckDestroy: testAccCheckDatabaseDestroy(ctx),
208+
Steps: []resource.TestStep{
209+
{
210+
Config: testAccCatalogDatabaseConfig_federatedDatabase(rName),
211+
Destroy: false,
212+
Check: resource.ComposeAggregateTestCheckFunc(
213+
testAccCheckCatalogDatabaseExists(ctx, resourceName),
214+
resource.TestCheckResourceAttr(resourceName, "name", rName),
215+
acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "glue", fmt.Sprintf("database/%s", rName)),
216+
resource.TestCheckResourceAttr(resourceName, "federated_database.#", "1"),
217+
resource.TestCheckResourceAttr(resourceName, "federated_database.0.connection_name", "aws:redshift"),
218+
acctest.MatchResourceAttrRegionalARN(resourceName, "federated_database.0.identifier", "redshift", regexache.MustCompile(`datashare:+.`)),
219+
),
220+
},
221+
{
222+
ResourceName: resourceName,
223+
ImportState: true,
224+
ImportStateVerify: true,
225+
},
226+
},
227+
})
228+
}
229+
197230
func TestAccGlueCatalogDatabase_tags(t *testing.T) {
198231
ctx := acctest.Context(t)
199232
resourceName := "aws_glue_catalog_database.test"
@@ -320,6 +353,90 @@ resource "aws_glue_catalog_database" "test" {
320353
`, rName, desc)
321354
}
322355

356+
func testAccCatalogDatabaseConfig_federatedDatabase(rName string) string {
357+
return acctest.ConfigCompose(
358+
fmt.Sprintf(`
359+
data "aws_region" "current" {}
360+
data "aws_partition" "current" {}
361+
data "aws_caller_identity" "current" {}
362+
363+
resource "aws_redshiftserverless_namespace" "test" {
364+
namespace_name = %[1]q
365+
db_name = "test"
366+
}
367+
368+
resource "aws_redshiftserverless_workgroup" "test" {
369+
namespace_name = aws_redshiftserverless_namespace.test.namespace_name
370+
workgroup_name = %[1]q
371+
}
372+
373+
resource "aws_redshiftdata_statement" "test_create" {
374+
workgroup_name = aws_redshiftserverless_workgroup.test.workgroup_name
375+
database = aws_redshiftserverless_namespace.test.db_name
376+
sql = "CREATE DATASHARE tfacctest;"
377+
}
378+
`, rName),
379+
// Split this resource into a string literal so the terraform `format` function
380+
// interpolates properly
381+
`
382+
resource "aws_redshiftdata_statement" "test_grant_usage" {
383+
depends_on = [aws_redshiftdata_statement.test_create]
384+
workgroup_name = aws_redshiftserverless_workgroup.test.workgroup_name
385+
database = aws_redshiftserverless_namespace.test.db_name
386+
sql = format("GRANT USAGE ON DATASHARE tfacctest TO ACCOUNT '%s' VIA DATA CATALOG;", data.aws_caller_identity.current.account_id)
387+
}
388+
389+
locals {
390+
# Data share ARN is not returned from the GRANT USAGE statement, so must be
391+
# composed manually.
392+
# Ref: https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonredshift.html#amazonredshift-resources-for-iam-policies
393+
data_share_arn = format("arn:%s:redshift:%s:%s:datashare:%s/%s",
394+
data.aws_partition.current.id,
395+
data.aws_region.current.name,
396+
data.aws_caller_identity.current.account_id,
397+
aws_redshiftserverless_namespace.test.namespace_id,
398+
"tfacctest",
399+
)
400+
}
401+
402+
resource "aws_redshift_data_share_authorization" "test" {
403+
depends_on = [aws_redshiftdata_statement.test_grant_usage]
404+
405+
data_share_arn = local.data_share_arn
406+
consumer_identifier = format("DataCatalog/%s", data.aws_caller_identity.current.account_id)
407+
}
408+
409+
resource "aws_redshift_data_share_consumer_association" "test" {
410+
depends_on = [aws_redshift_data_share_authorization.test]
411+
412+
data_share_arn = local.data_share_arn
413+
consumer_arn = format("arn:%s:glue:%s:%s:catalog",
414+
data.aws_partition.current.id,
415+
data.aws_region.current.name,
416+
data.aws_caller_identity.current.account_id,
417+
)
418+
}
419+
420+
resource "aws_lakeformation_resource" "test" {
421+
depends_on = [aws_redshift_data_share_consumer_association.test]
422+
423+
arn = local.data_share_arn
424+
use_service_linked_role = false
425+
}
426+
`,
427+
fmt.Sprintf(`
428+
resource "aws_glue_catalog_database" "test" {
429+
depends_on = [aws_lakeformation_resource.test]
430+
431+
name = %[1]q
432+
federated_database {
433+
connection_name = "aws:redshift"
434+
identifier = local.data_share_arn
435+
}
436+
}
437+
`, rName))
438+
}
439+
323440
func testAccCatalogDatabaseConfig_target(rName string) string {
324441
return fmt.Sprintf(`
325442
resource "aws_glue_catalog_database" "test" {

internal/service/lakeformation/resource.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func resourceResourceCreate(ctx context.Context, d *schema.ResourceData, meta in
8585
input.UseServiceLinkedRole = aws.Bool(true)
8686
}
8787

88-
if v, ok := d.GetOk("use_service_linked_role"); ok {
88+
if v, ok := d.GetOkExists("use_service_linked_role"); ok {
8989
input.UseServiceLinkedRole = aws.Bool(v.(bool))
9090
}
9191

website/docs/r/glue_catalog_database.html.markdown

+8-2
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ Provides a Glue Catalog Database Resource. You can refer to the [Glue Developer
1313
## Example Usage
1414

1515
```terraform
16-
resource "aws_glue_catalog_database" "aws_glue_catalog_database" {
16+
resource "aws_glue_catalog_database" "example" {
1717
name = "MyCatalogDatabase"
1818
}
1919
```
2020

2121
### Create Table Default Permissions
2222

2323
```terraform
24-
resource "aws_glue_catalog_database" "aws_glue_catalog_database" {
24+
resource "aws_glue_catalog_database" "example" {
2525
name = "MyCatalogDatabase"
2626
2727
create_table_default_permission {
@@ -41,12 +41,18 @@ This resource supports the following arguments:
4141
* `catalog_id` - (Optional) ID of the Glue Catalog to create the database in. If omitted, this defaults to the AWS Account ID.
4242
* `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.
4343
* `description` - (Optional) Description of the database.
44+
* `federated_database` - (Optional) Configuration block that references an entity outside the AWS Glue Data Catalog. See [`federated_database`](#federated_database) below.
4445
* `location_uri` - (Optional) Location of the database (for example, an HDFS path).
4546
* `name` - (Required) Name of the database. The acceptable characters are lowercase letters, numbers, and the underscore character.
4647
* `parameters` - (Optional) List of key-value pairs that define parameters and properties of the database.
4748
* `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.
4849
* `target_database` - (Optional) Configuration block for a target database for resource linking. See [`target_database`](#target_database) below.
4950

51+
### federated_database
52+
53+
* `connection_name` - (Optional) Name of the connection to the external metastore.
54+
* `identifier` - (Optional) Unique identifier for the federated database.
55+
5056
### target_database
5157

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

0 commit comments

Comments
 (0)