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/athena_database - add acl_configuration and expected_bucket_owner args #23745

Merged
merged 21 commits into from
Mar 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
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