Skip to content

Commit

Permalink
Merge pull request #23745 from DrFaust92/athena-database
Browse files Browse the repository at this point in the history
r/athena_database - add `acl_configuration` and `expected_bucket_owner` args
  • Loading branch information
ewbankkit authored Mar 18, 2022
2 parents f127a11 + 9fd4b4d commit 008a2d8
Show file tree
Hide file tree
Showing 5 changed files with 444 additions and 185 deletions.
15 changes: 15 additions & 0 deletions .changelog/23745.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
```release-note:enhancement
resource/aws_athena_database: Add `acl_configuration` and `expected_bucket_owner` arguments
```

```release-note:bug
resource/aws_athena_database: Remove from state on resource Read if deleted outside of Terraform
```

```release-note:enhancement
resource/aws_athena_database: Do not recreate the resource if `bucket` changes
```

```release-note:enhancement
resource/aws_athena_database: Add `comment` argument to support database descriptions
```
200 changes: 129 additions & 71 deletions internal/service/athena/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package athena

import (
"fmt"
"log"
"regexp"
"strings"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/athena"
"github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/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"
Expand All @@ -18,161 +20,217 @@ func ResourceDatabase() *schema.Resource {
return &schema.Resource{
Create: resourceDatabaseCreate,
Read: resourceDatabaseRead,
Update: resourceDatabaseUpdate,
Update: schema.Noop,
Delete: resourceDatabaseDelete,

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringMatch(regexp.MustCompile("^[_a-z0-9]+$"), "must be lowercase letters, numbers, or underscore ('_')"),
"acl_configuration": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"s3_acl_option": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice(athena.S3AclOption_Values(), false),
ForceNew: true,
},
},
},
},
"bucket": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Optional: true,
},
"force_destroy": {
Type: schema.TypeBool,
"comment": {
Type: schema.TypeString,
Optional: true,
Default: false,
ForceNew: true,
},
"encryption_configuration": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"encryption_option": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice(athena.EncryptionOption_Values(), false),
ForceNew: true,
},
"kms_key": {
Type: schema.TypeString,
Optional: true,
},
"encryption_option": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
athena.EncryptionOptionCseKms,
athena.EncryptionOptionSseKms,
athena.EncryptionOptionSseS3,
}, false),
ForceNew: true,
},
},
},
},
"expected_bucket_owner": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"force_destroy": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringMatch(regexp.MustCompile("^[_a-z0-9]+$"), "must be lowercase letters, numbers, or underscore ('_')"),
},
},
}
}

func expandAthenaResultConfiguration(bucket string, encryptionConfigurationList []interface{}) *athena.ResultConfiguration {
resultConfig := athena.ResultConfiguration{
OutputLocation: aws.String("s3://" + bucket),
}

if len(encryptionConfigurationList) <= 0 {
return &resultConfig
}

data := encryptionConfigurationList[0].(map[string]interface{})
keyType := data["encryption_option"].(string)
keyID := data["kms_key"].(string)
func resourceDatabaseCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).AthenaConn

encryptionConfig := athena.EncryptionConfiguration{
EncryptionOption: aws.String(keyType),
}
name := d.Get("name").(string)
var queryString string

if len(keyID) > 0 {
encryptionConfig.KmsKey = aws.String(keyID)
if v, ok := d.GetOk("comment"); ok {
queryString = fmt.Sprintf("create database `%[1]s` comment '%[2]s';", name, strings.Replace(v.(string), "'", "\\'", -1))
} else {
queryString = fmt.Sprintf("create database `%[1]s`;", name)
}

resultConfig.EncryptionConfiguration = &encryptionConfig

return &resultConfig
}

func resourceDatabaseCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).AthenaConn

input := &athena.StartQueryExecutionInput{
QueryString: aws.String(fmt.Sprintf("create database `%s`;", d.Get("name").(string))),
ResultConfiguration: expandAthenaResultConfiguration(d.Get("bucket").(string), d.Get("encryption_configuration").([]interface{})),
QueryString: aws.String(queryString),
ResultConfiguration: expandAthenaResultConfiguration(d),
}

resp, err := conn.StartQueryExecution(input)

if err != nil {
return err
return fmt.Errorf("error starting Athena Database (%s) query execution: %w", name, err)
}

if err := executeAndExpectNoRowsWhenCreate(*resp.QueryExecutionId, conn); err != nil {
if err := executeAndExpectNoRows(*resp.QueryExecutionId, "create", conn); err != nil {
return err
}
d.SetId(d.Get("name").(string))

d.SetId(name)

return resourceDatabaseRead(d, meta)
}

func resourceDatabaseRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).AthenaConn

input := &athena.GetDatabaseInput{
DatabaseName: aws.String(d.Get("name").(string)),
DatabaseName: aws.String(d.Id()),
CatalogName: aws.String("AwsDataCatalog"),
}
_, err := conn.GetDatabase(input)
res, err := conn.GetDatabase(input)

if tfawserr.ErrMessageContains(err, athena.ErrCodeMetadataException, "not found") && !d.IsNewResource() {
log.Printf("[WARN] Athena Database (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

if err != nil {
return err
return fmt.Errorf("error reading Athena Database (%s): %w", d.Id(), err)
}
return nil
}

func resourceDatabaseUpdate(d *schema.ResourceData, meta interface{}) error {
return resourceDatabaseRead(d, meta)
db := res.Database

d.Set("name", db.Name)

return nil
}

func resourceDatabaseDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).AthenaConn

name := d.Get("name").(string)

queryString := fmt.Sprintf("drop database `%s`", name)
queryString := fmt.Sprintf("drop database `%s`", d.Id())
if d.Get("force_destroy").(bool) {
queryString += " cascade"
}
queryString += ";"

input := &athena.StartQueryExecutionInput{
QueryString: aws.String(queryString),
ResultConfiguration: expandAthenaResultConfiguration(d.Get("bucket").(string), d.Get("encryption_configuration").([]interface{})),
ResultConfiguration: expandAthenaResultConfiguration(d),
}

resp, err := conn.StartQueryExecution(input)
if err != nil {
return err
}

if err := executeAndExpectNoRowsWhenDrop(*resp.QueryExecutionId, conn); err != nil {
if err := executeAndExpectNoRows(*resp.QueryExecutionId, "delete", conn); err != nil {
return err
}

return nil
}

func executeAndExpectNoRowsWhenCreate(qeid string, conn *athena.Athena) error {
rs, err := QueryExecutionResult(qeid, conn)
if err != nil {
return err
func expandAthenaResultConfiguration(d *schema.ResourceData) *athena.ResultConfiguration {

resultConfig := &athena.ResultConfiguration{
OutputLocation: aws.String("s3://" + d.Get("bucket").(string)),
EncryptionConfiguration: expandAthenaResultConfigurationEncryptionConfig(d.Get("encryption_configuration").([]interface{})),
}
if len(rs.Rows) != 0 {
return fmt.Errorf("Athena create database, unexpected query result: %s", flattenAthenaResultSet(rs))

if v, ok := d.GetOk("expected_bucket_owner"); ok {
resultConfig.ExpectedBucketOwner = aws.String(v.(string))
}
return nil

if v, ok := d.GetOk("acl_configuration"); ok && len(v.([]interface{})) > 0 {
resultConfig.AclConfiguration = expandAthenaResultConfigurationAclConfig(v.([]interface{}))
}

return resultConfig
}

func expandAthenaResultConfigurationEncryptionConfig(config []interface{}) *athena.EncryptionConfiguration {
if len(config) <= 0 {
return nil
}

data := config[0].(map[string]interface{})

encryptionConfig := &athena.EncryptionConfiguration{
EncryptionOption: aws.String(data["encryption_option"].(string)),
}

if v, ok := data["kms_key"].(string); ok && v != "" {
encryptionConfig.KmsKey = aws.String(v)
}

return encryptionConfig
}

func expandAthenaResultConfigurationAclConfig(config []interface{}) *athena.AclConfiguration {
if len(config) <= 0 {
return nil
}

data := config[0].(map[string]interface{})

encryptionConfig := &athena.AclConfiguration{
S3AclOption: aws.String(data["s3_acl_option"].(string)),
}

return encryptionConfig
}

func executeAndExpectNoRowsWhenDrop(qeid string, conn *athena.Athena) error {
func executeAndExpectNoRows(qeid, action string, conn *athena.Athena) error {
rs, err := QueryExecutionResult(qeid, conn)
if err != nil {
return err
}
if len(rs.Rows) != 0 {
return fmt.Errorf("Athena drop database, unexpected query result: %s", flattenAthenaResultSet(rs))
return fmt.Errorf("Athena %s database, unexpected query result: %s", action, flattenAthenaResultSet(rs))
}
return nil
}
Expand Down
Loading

0 comments on commit 008a2d8

Please sign in to comment.